Entrée
Bonjour cher lecteur!
Il y a quelque temps (environ un an), j'ai été confronté à la nécessité de rendre conditionnellement des composants dans ReactJS, en fonction des droits d'utilisateur actuels. La première chose que j'ai commencé à chercher des solutions toutes faites et des "meilleures pratiques". L'article «
Autorisation basée sur les rôles dans React » a le plus impressionné par son utilisation de
composants d'ordre supérieur (HOC). Mais, malheureusement, je n'ai pas trouvé de solution qui me satisfasse.
Apparemment, après tout, il a raté quelque chose ...... ou ne connaissait pas l'existence de contextes. Au moment d'écrire ces lignes, je suis tombé sur une merveilleuse
réponse dans stackoverflow . Je me suis retrouvé avec une solution très similaire.
À cette époque, j'étais un peu familier avec react-redux-connect (module npm), et j'étais très accro à l'approche de décoration utilisée dans la fonction connect. Une analyse détaillée du périphérique de connexion peut être trouvée
ici .
Description de la solution
Vous devez d'abord déterminer les informations minimales nécessaires pour prendre une décision concernant le rendu d'un composant. Évidemment, pour le rendu, il est nécessaire de remplir une condition (en option, il existe une sorte de droit - par exemple, le droit d'ajouter un nouvel utilisateur). Nous appelons cette condition une
exigence (ou exigence en anglais). Pour comprendre si l'exigence a été satisfaite, nous pouvons nous baser sur un
ensemble de droits d' utilisateur
actuels - les
informations d'identification . Autrement dit, il suffit de définir une fonction:
function isSatisfied(requirement, credentials) { if (...) { return false; } return true; }
Nous avons maintenant plus ou moins décidé de la condition de rendu. Comment l'utiliser?
1. Nous pouvons utiliser l'approche frontale:
const requirement = {...}; class App extends Component { render() { const {credentials} = this.props; return isSatisfied(requirement, credentials) && <TargetComponent>; } }
2. Nous pouvons aller un peu plus loin, et envelopper le composant cible dans un autre, qui fera la vérification de l'exigence:
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/>; } }
L'écriture manuelle d'un wrapper pour chaque composant cible est assez morne. Comment simplifier cela?
3. On peut recourir au mécanisme HOC (par analogie avec connect depuis "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> ... ); } }
Déjà mieux, mais toujours misérable - vous devez lancer manuellement les informations d'identification dans toute l'arborescence des composants. Que peut-on faire avec ça? Il est logique de supposer que les informations d'identification de l'utilisateur actuel sont un objet global pour l'application entière. Ensuite, react-redux-connect vient à la rescousse. Après avoir lu un article sur le périphérique de ce module, nous constatons qu'il utilise certains
contextes ReactJS.
4. En utilisant le mécanisme de contexte, nous obtenons l'approche finale:
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> ); } }
Postface
Ce fut une brève digression dans l'idée elle-même. Sur la base de cette idée, un module a été implémenté (
github ,
npm ), qui fournit des fonctionnalités plus intéressantes et est plus facile à intégrer (voir README.md dans le github et
démo utilisant le module).
Seulement pour une raison quelconque, je n'ai pas pu obtenir le package npm créé dans la démo, j'ai donc dû y insérer le code du module. Mais le module installé via
npm install react-rbac-guard fonctionne localement (Chrome 69.0.3497.100). Je soupçonne que le problème est dans la méthode de construction - je viens de copier les fichiers package.json et webpack.config.prod.js à partir d'un
module avec un objectif similaire.
Étant donné que je ne suis pas un développeur front-end, il reste encore beaucoup de travail inachevé (manque de tests, inopérabilité dans
https://codesandbox.io et, éventuellement, d'autres points manqués). Par conséquent, s'il y a des commentaires, des suggestions ou des demandes de tirage, alors bienvenue!
PPS: Tous les commentaires concernant l'orthographe, y compris dans README.md, veuillez envoyer des messages personnels ou sous forme de pull request.