Eintrag
Hallo lieber Leser!
Vor einiger Zeit (ungefähr einem Jahr) war ich mit der Notwendigkeit konfrontiert, Komponenten in ReactJS abhängig von den aktuellen Benutzerrechten bedingt zu rendern. Als erstes suchte ich nach vorgefertigten Lösungen und "Best Practices". Der Artikel "
Rollenbasierte Autorisierung in React " beeindruckte am meisten durch die Verwendung von
Komponenten höherer Ordnung (HOC). Leider habe ich keine Lösung gefunden, die mich zufriedenstellt.
Anscheinend hat er doch etwas verpasst ...... oder wusste nichts über die Existenz von Kontexten. Zum Zeitpunkt des Schreibens stieß ich
im Stackoverflow auf eine wunderbare
Antwort . Am Ende hatte ich eine sehr ähnliche Lösung.
Zu dieser Zeit war ich ein wenig mit dem React-Redux-Connect (npm-Modul) vertraut und war stark von dem Dekorationsansatz begeistert, der in der Connect-Funktion verwendet wurde. Eine detaillierte Analyse des Verbindungsgeräts finden Sie
hier .
Lösungsbeschreibung
Zuerst müssen Sie bestimmen, welche minimalen Informationen erforderlich sind, um eine Entscheidung über das Rendern einer Komponente zu treffen. Zum Rendern ist es natürlich erforderlich, eine bestimmte Bedingung zu erfüllen (optional gibt es eine Art Recht - zum Beispiel das Recht, einen neuen Benutzer hinzuzufügen). Wir nennen diese Bedingung eine
Anforderung (oder Anforderung in Englisch). Um zu verstehen, ob die Anforderung erfüllt wurde, können wir auf einer
Reihe aktueller Benutzerrechte basieren -
Anmeldeinformationen . Das heißt, es reicht aus, eine Funktion zu definieren:
function isSatisfied(requirement, credentials) { if (...) { return false; } return true; }
Jetzt haben wir uns mehr oder weniger für die Renderbedingung entschieden. Wie benutzt man es?
1. Wir können den Stirnansatz verwenden:
const requirement = {...}; class App extends Component { render() { const {credentials} = this.props; return isSatisfied(requirement, credentials) && <TargetComponent>; } }
2. Wir können noch einen Schritt weiter gehen und die Zielkomponente in eine andere einwickeln, um die Anforderung zu überprüfen:
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/>; } }
Das manuelle Schreiben eines Wrappers für jede Zielkomponente ist ziemlich trostlos. Wie können wir das vereinfachen?
3. Wir können auf den HOC-Mechanismus zurückgreifen (analog zu connect von "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> ... ); } }
Bereits besser, aber immer noch elend - Sie müssen Anmeldeinformationen manuell durch den gesamten Komponentenbaum werfen. Was kann man damit machen? Es ist logisch anzunehmen, dass die Anmeldeinformationen des aktuellen Benutzers ein globales Objekt für die gesamte Anwendung sind. Dann kommt wieder React-Redux-Connect zur Rettung. Nachdem wir einen Artikel über das Gerät dieses Moduls gelesen haben, stellen wir fest, dass es einige ReactJS-
Kontexte verwendet .
4. Unter Verwendung des Kontextmechanismus erhalten wir den endgültigen Ansatz:
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> ); } }
Nachwort
Es war ein kurzer Exkurs in die Idee selbst. Basierend auf dieser Idee wurde ein Modul implementiert (
github ,
npm ), das interessantere Funktionen bietet und einfacher einzubetten ist (siehe README.md im Github und in der
Demo mit dem Modul).
Nur aus irgendeinem Grund konnte ich das erstellte npm-Paket nicht in die Demo aufnehmen, daher musste ich dort den Modulcode einfügen. Das über
npm install react-rbac-guard installierte Modul funktioniert jedoch lokal (Chrome 69.0.3497.100). Ich vermute, dass das Problem in der Erstellungsmethode liegt - ich habe gerade die Dateien package.json und webpack.config.prod.js von einem
Modul mit einem ähnlichen Zweck kopiert.
Da ich kein Front-End-Entwickler bin, gibt es noch viel unvollendete Arbeit (fehlende Tests, Inoperabilität in
https://codesandbox.io und möglicherweise andere verpasste Punkte). Daher, wenn es Kommentare, Vorschläge oder Pull-Anfragen gibt, dann willkommen!
PPS: Alle Kommentare zur Rechtschreibung, einschließlich in README.md, senden Sie bitte in persönlichen Nachrichten oder in Form einer Pull-Anfrage.