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.