在构建复杂的 Web 应用时,经常会遇到需要基于用户权限来控制元素事件访问性的情况。这不仅涉及到用户体验的提升,更关乎应用安全性的加固。JavaScript 和 React 提供了灵活的事件处理机制,特别是通过利用事件的捕获阶段来阻止事件传播可以实现精细的权限控制。本文将详细介绍这一技术的应用,并通过实践案例展示如何封装和使用一个 React 鉴权组件。
1. 在事件的捕获阶段阻止事件的传播
为了更好地理解如何在捕获阶段阻止事件的传播,让我们先从一个完整的 HTML 页面示例开始:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Event Capture and Authorization</title>
<style>
.container { padding: 20px; background-color: #f0f0f0; }
.button { padding: 10px; background-color: #007bff; color: white; cursor: pointer; }
</style>
</head>
<body>
<div class="container" id="container">
Click Me
<div class="button" id="button">Authorized Action</div>
</div>
<script>
document.getElementById('container').addEventListener('click', function(event) {
alert('Container clicked!');
}, true); // Use capture phase
document.getElementById('button').addEventListener('click', function(event) {
event.stopPropagation(); // Stop event from reaching the container
alert('Button clicked!');
}, false); // Use bubble phase
</script>
</body>
</html>
在这个示例中,我们有一个容器和一个按钮。当按钮在没有授权的情况下被点击时,我们通过在捕获阶段为容器添加的事件监听器中调用 event.stopPropagation()
方法,阻止了事件向上传播。这样,即使按钮被点击,容器的点击事件也不会被触发,从而实现了基于权限的事件控制。
2. 阻止事件传播的意义
阻止事件在捕获阶段的传播不仅是一个技术行为,它背后的意义更加深远。首先,从安全性角度考虑,这可以防止未授权的用户触发敏感操作,如删除数据、修改配置等,从而有效地防范安全风险。其次,从用户体验角度来看,合理地控制事件传播可以避免用户误操作,尤其是在复杂的交互设计中,能够确保应用的操作逻辑更加清晰和稳定。最后,这种机制还提供了一种灵活的事件管理方式,使开发者能够更精细地控制应用的行为和响应,增强了应用的可维护性和扩展性。
3. 封装一个鉴权组件
在构建React前端应用时,确保用户在执行特定操作前已通过身份验证是常见需求。React 提供了灵活的方式来处理这种情况,特别是结合事件捕获机制。以下是如何逐步封装一个利用事件捕获机制进行用户鉴权的 React 组件的详细解释。
3.1 使用 useRef 创建引用
const ref = useRef(null);
useRef
是一个 React 钩子(Hook),它创建了一个可变的 ref
对象,并将其赋值为 null
。这个 ref
对象可以被附加到 React 元素上,允许我们直接访问 DOM 元素。在我们的鉴权组件中,这使得我们能够直接在 DOM 元素上添加和移除事件监听器。
3.2 定义事件监听器并在捕获阶段注册
useEffect(() => {
const handleClickCapture = (e) => {
if(disabled) return;
if(!isUserLogin(window.loginInfo && window.loginInfo.userInfo)) {
e.stopPropagation();
Modal.clear();
Modal.confirm({
bodyClassName: "modal-confirm-body",
content: intl.get("helptext_login_first"),
cancelText: intl.get("text_cancel"),
confirmText: intl.get("text_log_in"),
onConfirm: () => native_LoginAgain.call()
});
}
};
const currentElement = ref.current;
currentElement.addEventListener('click', handleClickCapture, true);
return () => {
currentElement.removeEventListener('click', handleClickCapture, true);
};
}, [disabled]);
在 useEffect
钩子内,我们定义了一个 handleClickCapture
函数,该函数会在点击事件发生时被调用。通过设置 addEventListener
的第三个参数为 true
,我们指示浏览器在捕获阶段触发此监听器,而不是冒泡阶段。这使我们能够在事件向下传播到目标元素之前拦截它。
如果组件被标记为 disabled
,handleClickCapture
函数会立即返回,不执行任何操作。如果用户未登录(通过 isUserLogin
函数检查),函数会调用 e.stopPropagation()
阻止事件进一步传播,然后显示一个模态框(Modal
),提示用户登录。
useEffect
的清理函数确保在组件卸载时移除事件监听器,防止内存泄漏。
3.3 组件渲染
return (
<div
ref={ref}
className={className}
style={style}
onClick={(e) => {
if (!disabled && isUserLogin(window.loginInfo && window.loginInfo.userInfo)) {
onClick && onClick(e);
}
}}
>
{children}
</div>
);
在组件的返回语句中,我们将 ref
对象附加到 div
元素上。这样,之前定义的事件监听器就能够被正确注册到这个 div
上。我们还为这个 div
提供了一个 onClick
事件处理函数。在这个函数内部,如果组件未被禁用并且用户已登录,则调用传入的 onClick
回调。这保留了组件原有点击行为的可能性,但只在用户满足特定条件时触发。
小结
通过这种方式,就封装了一个可以在事件的捕获阶段根据用户登录状态决定是否允许事件进一步传播的 React 组件。这种模式不仅增强了应用的安全性,避免了未授权的操作,还提高了用户体验,通过模态框提示需要登录。这种鉴权组件的封装展示了 React 以及现代 JavaScript 提供的强大能力,使得开发高质量的 Web 应用更加高效和灵活。
完整组件代码
import React, { useEffect, useRef } from "react";
import { isUserLogin } from "../lib/common";
import { Modal } from "antd-mobile";
import intl from "react-intl-universal";
import { native_LoginAgain } from "../lib/nativeUtil";
export default function LoginAuth(props) {
const { disabled, onClick, style, className, children } = props;
const ref = useRef(null); // 使用 useRef 创建一个 ref 对象
useEffect(() => {
// 定义一个在捕获阶段执行的 handleClick 函数
const handleClickCapture = (e) => {
if(disabled) return;
if(!isUserLogin(window.loginInfo && window.loginInfo.userInfo)) {
e.stopPropagation(); // 阻止事件在捕获阶段继续传播
Modal.clear();
Modal.confirm({
bodyClassName: "modal-confirm-body",
content: intl.get("helptext_login_first"),
cancelText: intl.get("text_cancel"),
confirmText: intl.get("text_log_in"),
onConfirm: () => native_LoginAgain.call()
});
}
};
// 获取当前 ref 指向的 DOM 元素
const currentElement = ref.current;
// 为该元素添加捕获阶段的事件监听器
currentElement.addEventListener('click', handleClickCapture, true);
// 清理函数:组件卸载时移除事件监听器
return () => {
currentElement.removeEventListener('click', handleClickCapture, true);
};
}, [disabled]); // 依赖项数组中的元素会触发 useEffect 的重新执行
return (
<div
ref={ref} // 将 ref 绑定到 div 元素上
className={className}
style={style}
onClick={(e) => {
// 阻止冒泡阶段的处理,如果需要
if (!disabled && isUserLogin(window.loginInfo && window.loginInfo.userInfo)) {
onClick && onClick(e);
}
}}
>
{children}
</div>
);
}
结尾
通过在事件的捕获阶段阻止事件传播,结合 React 组件的封装,不仅能够有效控制应用中的权限逻辑,还能提升用户体验和应用安全性。这种技术的应用展示了现代前端开发中,事件处理机制的强大能力,以及通过合理设计提高应用整体质量的可能性。在开发实践中灵活运用这一技术,将有助于构建更加稳定、安全且用户友好的 Web 应用。
原文链接:https://juejin.cn/post/7343137522069602313 作者:慕仲卿