web-dev-qa-db-ja.com

ユーザーの役割に応じて、いくつかのReactコンポーネントの子を非表示にする

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' }権限を持っている場合はModelsコンポーネントの編集ボタンを非表示にします)。

私は この解決策 を見つけ、私のタスクのために少し変更します:

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>

したがって、ユーザーがwriteに対するmodels権限を持っていない場合、ボタンは表示されません。

誰かがこのようなことをしましたか?これよりも「エレガントな」ソリューションはありますか?

ありがとう!

P.S。もちろん、ユーザー権限もサーバー側でチェックされます。

9
Anton Novik

さて、私はあなたが欲しいものを理解したと思います。私は自分に合った何かをしました、そして私はそれを持っている方法が好きです、しかし私は他の実行可能な解決策がそこにあることを理解しています。

私が書いたのはHOCのreact-routerスタイルでした。

基本的に、ユーザーのアクセス許可を初期化するPermissionsProviderがあります。以前に提供した権限をコンポーネントに注入する別のwithPermissionsHOCがあります。

したがって、その特定のコンポーネントのアクセス許可を確認する必要がある場合は、簡単にアクセスできます。

// 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です(詳細は ここ を学ぶことができます)。

高階コンポーネント(HOC)は、コンポーネントロジックを再利用するためのReactの高度な手法です。HOC自体はReact APIの一部ではありません。は、Reactの構成的性質から生まれるパターンです。具体的には、高階コンポーネントは、コンポーネントを受け取り、新しいコンポーネントを返す関数です。

基本的に私がこれをしたのは、react-routerが行ったことに触発されたからです。ルーティングに関する情報が必要な場合はいつでも、デコレータ@ withRouterを追加するだけで、コンポーネントに小道具を挿入できます。 では、同じことをしてみませんか?

  1. まず、プロバイダーとのアクセス許可を設定する必要があります
  2. 次に、パーミッションをチェックするコンポーネントで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を確認します
  • それは本当に長くて複雑な答えであり、私は自分の最善の能力で明確に表現しようとしました。あちこちでいくつかの間違いのために私を焼かないでください:)
8
João Cunha

@lokuztによって提案されたアプローチは素晴らしいです。

また、コードを簡素化するためにさらに先に進むことができます。

まず第一に、すべての保護されたコンポーネントにはいくつかがあります 要件 レンダリングに満足する。レンダリングにrequirementを取り、credentialsをとる関数を定義する必要があります。パラメータとしての現在のユーザー。 trueまたはfalseを返す必要があります。

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

  return true;
}

さらに、ReactJSの 新しいコンテキストAPI を使用して HOC(高階コンポーネント) を定義する必要があります。

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です。

デモ もご利用いただけます。

4
n.saktaganov