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,但是有一个新的问题

js 复制代码
const MyWrapper = ({ children }) => {
  return visible ? children(disabled) : null
}

// 使用
<MyWrapper auth="home.detail.edit">
  {({ disabled }) => ( <button disabled={disabled}>按钮</button> )}
</MyWrapper>

要用 propsRender 的方式穿参,如果每个地方都这样写,那也太奇怪了吧!!

经过一番查询,找到了方法

js 复制代码
const MyWrapper = ({ children, auth }) => {
  return React.Children.map(children, child => { 
    // 将disabled属性传递给子组件 
    return React.cloneElement(child, { disabled }); 
});
}

但是 MenuItem 的 disbled 属性怎么办呢?继续递归处理。 完整的测试代码如下:(代码使用"码上掘金"编辑器编写,可以直接复制进编辑器运行)

js 复制代码
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'));
      
      
相关推荐
掘金安东尼14 分钟前
前端周刊第421期(2025年7月1日–7月6日)
前端·面试·github
摸鱼仙人~17 分钟前
深入理解 classnames:React 动态类名管理的最佳实践
前端·react.js·前端框架
未来之窗软件服务19 分钟前
chrome webdrive异常处理-session not created falled opening key——仙盟创梦IDE
前端·人工智能·chrome·仙盟创梦ide·东方仙盟·数据调式
kymjs张涛19 分钟前
零一开源|前沿技术周报 #6
前端·ios·harmonyos
玲小珑23 分钟前
Next.js 教程系列(十)getStaticPaths 与动态路由的静态生成
前端·next.js
天天鸭29 分钟前
写个vite插件自动处理系统权限,降低99%重复工作
前端·javascript·vite
蓝婷儿34 分钟前
每天一个前端小知识 Day 23 - PWA 渐进式 Web 应用开发
前端
无奈何杨43 分钟前
CoolGuard风控中新增移动距离和移动速度指标
前端·后端
恋猫de小郭1 小时前
Google I/O Extended :2025 Flutter 的现状与未来
android·前端·flutter
江城开朗的豌豆1 小时前
Vue-router方法大全:让页面跳转随心所欲!
前端·javascript·vue.js