Abordagem de implementação do ReactJS RBAC

Entrada


Olá querido leitor!

Há algum tempo (cerca de um ano) atrás, eu fui confrontado com a necessidade de renderizar condicionalmente componentes no ReactJS, dependendo dos direitos atuais do usuário. A primeira coisa que comecei a procurar soluções prontas e "melhores práticas". O artigo " Autorização baseada em função no React " impressionou mais com o uso de componentes de ordem superior (HOC). Mas, infelizmente, não encontrei uma solução que me satisfaça.

Aparentemente, afinal, ele perdeu alguma coisa ...
... ou não sabia da existência de contextos. No momento em que escrevi, encontrei uma resposta maravilhosa no stackoverflow . Acabei com uma solução muito semelhante.

Naquela época, eu estava um pouco familiarizado com o react-redux-connect (módulo npm) e fiquei bastante entusiasmado com a abordagem de decoração usada na função de conexão. Uma análise detalhada do dispositivo de conexão pode ser encontrada aqui .

Descrição da solução


Primeiro, você precisa determinar quais informações mínimas são necessárias para tomar uma decisão sobre a renderização de um componente. Obviamente, para renderizar, é necessário preencher alguma condição (como opção, existe algum tipo de direito - por exemplo, o direito de adicionar um novo usuário). Chamamos essa condição de requisito (ou requisito em inglês). Para entender se o requisito foi atendido, podemos basear-se em um conjunto de direitos de usuário atuais - credenciais . Ou seja, basta definir uma função:

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

Agora, decidimos mais ou menos sobre a condição de renderização. Como usá-lo?

1. Podemos usar a abordagem da testa:

 const requirement = {...}; class App extends Component { render() { const {credentials} = this.props; return isSatisfied(requirement, credentials) && <TargetComponent>; } } 

2. Podemos ir um pouco mais longe e envolver o componente de destino em outro, o que fará a verificação do requisito:

 const requirement = {...}; class ProtectedTargetComponent extends Component { render() { const {credentials} = this.props; return ( isSatisfied(requirement, credentials) ? <TargetComponent {...this.props}> {this.props.children} </TargetComponent> : null ); } } class App extends Component { render() { const {credentials} = this.props; return <ProtectedTargetComponent/>; } } 

Escrever manualmente um invólucro para cada componente de destino é bastante sombrio. Como podemos simplificar isso?

3. Podemos recorrer ao mecanismo HOC (por analogia, com connect de "react-redux-connect"):

 function protect(requirement, WrappedComponent) { return class extends Component { render() { const { credentials } = this.props; return ( isSatisfied(requirement, credentials) ? <WrappedComponent {...this.props}> {this.props.children} </WrappedComponent> : null ); } } } ... const requireAdmin = {...}; const AdminButton = protect(requireAdmin, Button); ... class App extends Component { render() { const {credentials} = this.props; return ( ... <AdminButton credentials={credentials}> Add user </AdminButton> ... ); } } 

Já é melhor, mas ainda é péssimo - você precisa lançar credenciais manualmente através de toda a árvore de componentes. O que pode ser feito com isso? É lógico supor que as credenciais do usuário atual sejam um objeto global para todo o aplicativo. Então, novamente, o react-redux-connect vem em socorro. Depois de ler um artigo sobre o dispositivo deste módulo, descobrimos que ele usa alguns contextos ReactJS.

4. Usando o mecanismo de contexto, obtemos a abordagem final:

 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 = {...}; const AdminButton = protect(requireAdmin, Button); ... class App extends Component { render() { const { credentials } = this.props; return ( <Provider value={credentials}> ... <AdminButton> Add user </AdminButton> ... </Provider> ); } } 

Posfácio


Foi uma breve digressão sobre a própria idéia. Com base nessa idéia, um módulo foi implementado ( github , npm ), que fornece recursos mais interessantes e é mais fácil de incorporar (consulte README.md no github e demo usando o módulo).

Somente por algum motivo não consegui obter o pacote npm criado na demonstração, então tive que inserir o código do módulo lá. Mas o módulo instalado por meio do npm install react-rbac-guard funciona localmente (Chrome 69.0.3497.100). Suspeito que o problema esteja no método de compilação - apenas copiei os arquivos package.json e webpack.config.prod.js de um módulo com um objetivo semelhante.

Como não sou desenvolvedor de front-end, ainda há muito trabalho inacabado (falta de testes, inoperabilidade em https://codesandbox.io e, possivelmente, outros pontos perdidos). Portanto, se houver comentários, sugestões ou solicitações de recebimento, seja bem-vindo!

PPS: todos os comentários sobre ortografia, inclusive no README.md, envie mensagens pessoais ou sob a forma de uma solicitação de recebimento.

Source: https://habr.com/ru/post/pt428143/


All Articles