根据用户角色隐藏一些 React 组件子项

Posted

技术标签:

【中文标题】根据用户角色隐藏一些 React 组件子项【英文标题】:Hide some React component children depending on user role 【发布时间】:2017-07-22 21:30:40 【问题描述】:

我正在用 React 和 Redux 编写一个单页应用程序(带有 Node.js 后端)。

我想实现基于角色的访问控制,并想控制应用的某些部分(或子部分)的显示。

我要从 Node.js 获取权限列表,它只是一个具有这种结构的对象:


  users: 'read',
  models: 'write',
  ...
  dictionaries: 'none',

key是受保护的资源,

value 是此资源的用户权限(其中之一:nonereadwrite)。

我将它存储到 redux 状态。看起来很简单。

none 权限将由react-router 路由onEnter/onChange 钩子或redux-auth-wrapper 检查。看起来也很简单。

但是将read/write 权限应用于任何组件视图的最佳方式是什么(例如,如果用户拥有 models: 'read' 权限,则隐藏模型组件中的编辑按钮)。

我找到了this solution 并根据我的任务对其进行了一些更改:

class Check extends React.Component 
  static propTypes = 
    resource: React.PropTypes.string.isRequired,
    permission: React.PropTypes.oneOf(['read', 'write']),
    userPermissions: React.PropTypes.object,
  ;

  // Checks that user permission for resource is the same or greater than required
  allowed() 
    const permissions = ['read', 'write'];
    const  permission, userPermissions  = this.props;
    const userPermission = userPermissions[resource] || 'none';

    return permissions.indexOf(userPermission) >= permissions.indexOf(permission)
  

  render() 
    if (this.allowed()) return  this.props.children ;
  


export default connect(userPermissionsSelector)(Check)

userPermissionsSelector 会是这样的:(store) => store.userPermisisons 并返回用户权限对象。

然后用Check包裹受保护的元素:

<Check resource="models" permission="write">
  <Button>Edit model</Button>
</Check>

因此,如果用户没有writemodels 权限,则不会显示该按钮。

有人做过这样的事吗?还有比这更“优雅”的解决方案吗?

谢谢!

P.S.当然也会在服务器端检查用户权限。

【问题讨论】:

我以前做过类似的事情。我有一个基于用户限制的管理页面。你所做的基本上就是我所做的。我使用 php 作为我的后端,并通过检查 PHP 会话来验证它(不是最好的,但可以使用我所拥有的/知道的)。它成功地隐藏了页面。如果您的解决方案有效,那么我不会对此大惊小怪,尽管您可能希望在渲染中使用 else 语句来返回类似 you do not have access to this page 或其他内容,以便用户可以看到某些内容,而不是思考页面坏了 谢谢,但在这种情况下不需要else 声明,因为我想在已经打开的页面中隐藏一些操作控件​​,例如“编辑”或“删除”按钮。 没关系,如果您执行 exists &amp;&amp; &lt;div&gt; some stuff &lt;/div&gt; 之类的操作并且存在是错误的,那么 div 将不存在(不仅仅是display: none 嘿...你找到这个解决方案的任何在线资源了吗? 嗨,我没有 【参考方案1】:

嗯,我想我明白你想要什么。我做了一些对我有用的事情,我喜欢我拥有它的方式,但我知道还有其他可行的解决方案。

我写的是 HOC react-router 风格。

基本上我有我的 PermissionsProvider 我在其中初始化用户权限。我有另一个 withPermissions HOC,它将我之前提供的权限注入到我的组件中。

因此,如果我需要检查该特定组件中的权限,我可以轻松访问它们。

// PermissionsProvider.js
import React,  Component  from "react";
import PropTypes from "prop-types";
import hoistStatics from "hoist-non-react-statics";

class PermissionsProvider extends React.Component 
  static propTypes = 
    permissions: PropTypes.array.isRequired,
  ;

  static contextTypes = 
    permissions: PropTypes.array,
  ;

  static childContextTypes = 
    permissions: PropTypes.array.isRequired,
  ;

  getChildContext() 
    // maybe you want to transform the permissions somehow
    // maybe run them through some helpers. situational stuff
    // otherwise just return the object with the props.permissions
    // const permissions = doSomething(this.props.permissions);
    // maybe add some validation methods
    return  permissions: this.props.permissions ;
  

  render() 
    return React.Children.only(this.props.children);
  


const withPermissions = Component => 
  const C = (props, context) => 
    const  wrappedComponentRef, ...remainingProps  = props;

    return (
      <Component permissions=context.permissions ...remainingProps ref=wrappedComponentRef />
    );
  ;

  C.displayName = `withPermissions($Component.displayName || Component.name)`;
  C.WrappedComponent = Component;
  C.propTypes = 
    wrappedComponentRef: PropTypes.func
  ;

  C.contextTypes = 
    permissions: PropTypes.array.isRequired
  ;

  return hoistStatics(C, Component);
;

export  PermissionsProvider as default, withPermissions ;

好的,我知道这是很多代码。但这些都是 HOC(您可以了解更多信息here)。

高阶组件 (HOC) 是 React 中的一种高级技术 重用组件逻辑。 HOC 本身并不是 React API 的一部分。 它们是从 React 的组合性质中出现的一种模式。 具体来说,高阶组件是一个函数,它接受一个 组件并返回一个新组件。

基本上我这样做是因为我受到了 react-router 所做的启发。 每当你想知道一些路由的东西时,你可以添加装饰器 @withRouter 并将道具注入你的组件。 那么为什么不做同样的事情呢?

    首先您必须通过提供者设置权限 然后在检查权限的组件上使用 withPermissions 装饰器

//App render
return (
   <PermissionsProvider permissions=permissions>
      <SomeStuff />
   </PermissionsProvider>
);

SomeStuff 的某个地方,您有一个广泛使用的工具栏来检查权限?

@withPermissions
export default class Toolbar extends React.Component 
  render() 
    const  permissions  = this.props;

    return permissions.canDoStuff ? <RenderStuff /> : <HeCantDoStuff />; 
  

如果你不能使用装饰器,你可以像这样导出工具栏

export default withPermissions(Toolbar);

这是我在实践中展示的代码框:

https://codesandbox.io/s/lxor8v3pkz

注意事项:

我确实简化了权限,因为该逻辑来自您的最终目的,出于演示目的,我对其进行了简化。 我假设权限是一个数组,这就是我检查 HOC 中的 PropTypes.array 的原因 这是一个非常冗长而复杂的答案,我试图尽我最大的能力表达出来。请不要在这里和那里因为一些错误而折磨我:)

【讨论】:

@AntonNovik 你的情况如何?【参考方案2】:

@lok​​uzt 建议的方法很棒。

您还可以进一步简化代码。

首先,每个受保护的组件都有一些要求来满足渲染。您需要定义一个函数,该函数将 requirement 渲染和当前用户的 credentials 作为参数。它必须返回 truefalse

function isSatisfied(requirement, credentials) 
  if (...) 
    return false;
  

  return true;

此外,我们必须使用来自 ReactJS 的 new context API 定义一个 HOC (Higher-Order Component)。

const  Provider, Consumer  = React.createContext();

function protect(requirement, WrappedComponent) 
  return class extends Component 
    render() 
      return (
        <Consumer>
           credentials => isSatisfied(requirement, credentials)
            ? <WrappedComponent ...this.props>
                this.props.children
              </WrappedComponent>
            : null
          
        </Consumer>
      );
    
  ; 

现在你可以装饰你的组件了:

const requireAdmin = ...; // <- this is your requirement

class AdminPanel extends Component 
  ...


export default protect(requireAdmin, AdminPanel);

甚至是第三方组件:

import Button from 'react-bootstrap';

const AdminButton = protect(requireAdmin, Button);

凭据必须由 ReactJS 上下文 API 传递:

class MyApp extends Component 
  render() 
    const credentials = this.props;

    <Provider value=credentials>
      ...

         <AdminPanel/>

         <AdminButton>
           Drop Database
         </AdminButton>
      ...
    </Provider>
  

这是我在 github 上的扩展 implementation。

demo 也可用。

【讨论】:

以上是关于根据用户角色隐藏一些 React 组件子项的主要内容,如果未能解决你的问题,请参考以下文章

如何隐藏其子项被隐藏的菜单?

[原创]React+Ant Design设置左侧菜单导航路由的显示与隐藏(与权限无关)

如何根据用户角色在后台隐藏操作按钮?

根据用户角色获取登录用户的django权限以显示/隐藏菜单

根据用户角色隐藏导航栏

显示/隐藏 React 组件不会保持内部状态