React权限控制(特殊组件&按钮)
react项目中需要实现一个按钮级权限,一开始想的是写一个 AuthButton 组件,然后直接判断权限就好了。 但是,没有这么容易,需求有些不一样...
- 有一些 TextArea 也需要实现(根据用户权限判断)可查看但不可编辑;
- Dropdown Menu 有些需要实现(根据用户权限判断)可查看但某些 Menu Item 不可编辑;
- 等等一些其他的特殊组件
思路1 —— 每种组件写一个 AuthComp 【太不好维护,每增加一种组件权限需要再写新的组件,组件多维护成本高】
思路2——写一个 AuthWrapper, 将 disabled 和 visible 参数透传进最后的 button 里【代码耦合度太高了,props 传递了太多层,需要修改多个组件,容易产生bug】
思路3 —— 写一个 AuthWrapper,将每个需要权限控制的组件 / 按钮的外层套一个AuthWrapper的壳。 【优点】:AuthWrapper 专注做用户权限相关交互,内部的 children 就只放 UI 组件相关,交互行为与UI组件相独立,耦合度低。
最后,我决定用 方案3,但是有一个新的问题
const MyWrapper = ({ children }) => {
return visible ? children(disabled) : null
}
// 使用
<MyWrapper auth="home.detail.edit">
{({ disabled }) => ( <button disabled={disabled}>按钮</button> )}
</MyWrapper>
要用 propsRender 的方式穿参,如果每个地方都这样写,那也太奇怪了吧!!
经过一番查询,找到了方法
const MyWrapper = ({ children, auth }) => {
return React.Children.map(children, child => {
// 将disabled属性传递给子组件
return React.cloneElement(child, { disabled });
});
}
import React, { useState, useCallback } from 'react';
import ReactDom from 'react-dom';
const MyWrapper = ({ children, auth }) => {
// 模拟权限列表,根据实际情况修改
const permissionList = ['home.detail.edit', 'some.other.permission'];
// 检查是否有权限
const hasPermission = (permission) => {
return permissionList.includes(permission);
};
const isVisible = permissionList.includes(auth);
const isEditable = permissionList.includes(auth);
// 根据权限信息决定是否禁用按钮
const disabled = !isEditable;
// 处理子组件
const handleChild = (child) => {
const disabled = !hasPermission(auth);
// 如果子组件是 <select> 元素
if (child.type === 'select') {
// 提取出 <select> 元素
// const selectElement = React.Children.only(child);
// 这一步是为了限制 select 整个组件的 disabled
const selectElement = React.cloneElement(child, { disabled })
// 处理 <option> 元素
const options = React.Children.map(selectElement.props.children, option => {
if (option.type === 'option' && option.props.auth) {
// 根据权限信息设置是否禁用,hidden处理子元素的隐藏
const optionDisabled = !hasPermission(option.props.auth);
return React.cloneElement(option, { disabled: optionDisabled , hidden: option.props.value === 'opel'});
}
return option;
});
// 返回处理后的 <select> 元素
return React.cloneElement(selectElement, null, options);
} else {
return React.cloneElement(child, { disabled });
}
};
// return isVisible ? React.Children.map(children, handleChild) : null
return React.Children.map(children, handleChild)
}
const Test = function () {
const [count, setCount] = useState(0);
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div onClick={handleClick}>
test
<MyWrapper auth="home.detail.edit">
<button>测试权限</button>
</MyWrapper>
<MyWrapper auth="home.detail.add">
<button>测试权限不可</button>
</MyWrapper>
<MyWrapper auth="home.detail.edit">
<input placeholder="测试可输入" />
</MyWrapper>
<MyWrapper auth="home.detail.add">
<input placeholder="不可输入" />
</MyWrapper>
<MyWrapper auth="home.detail.edit">
<select>
<option value="volvo" auth="home.detail.edit">Volvo</option>
<option value="saab" auth="home.detail.edit">Saab</option>
<option value="opel" auth="home.detail.add">Opel</option>
<option value="audi" auth="home.detail.add">Audi</option>
</select>
</MyWrapper>
<MyWrapper auth="home.detail.add">
<select>
<option value="volvo" auth="home.detail.edit">Volvo</option>
<option value="saab" auth="home.detail.edit">Saab</option>
<option value="opel" auth="home.detail.add">Opel</option>
<option value="audi" auth="home.detail.add">Audi</option>
</select>
</MyWrapper>
</div>
);
};
ReactDom.render(<Test />, document.getElementById('app'));
转载自:https://juejin.cn/post/7347956542308302860