Entrada
Hola querido lector!
Hace alg煤n tiempo (hace aproximadamente un a帽o), me enfrent茅 a la necesidad de renderizar condicionalmente componentes en ReactJS, dependiendo de los derechos de usuario actuales. Lo primero que comenc茅 a buscar era soluciones preparadas y "mejores pr谩cticas". El art铆culo "
Autorizaci贸n basada en roles en React " impresion贸 m谩s con su uso de
componentes de orden superior (HOC). Pero, desafortunadamente, no encontr茅 una soluci贸n que me satisfaga.
Aparentemente, despu茅s de todo, se perdi贸 algo ...... o no sab铆a sobre la existencia de contextos. Al momento de escribir, encontr茅 una
respuesta maravillosa
en stackoverflow . Termin茅 con una soluci贸n muy similar.
En ese momento, estaba un poco familiarizado con react-redux-connect (m贸dulo npm), y el enfoque de decoraci贸n utilizado en la funci贸n de conexi贸n me enganch贸 mucho. Un an谩lisis detallado del dispositivo de conexi贸n se puede encontrar
aqu铆 .
Descripci贸n de la soluci贸n
Primero debe determinar qu茅 informaci贸n m铆nima se necesita para tomar una decisi贸n sobre la representaci贸n de un componente. Obviamente, para renderizar es necesario cumplir alguna condici贸n (como opci贸n, hay alg煤n tipo de derecho, por ejemplo, el derecho de agregar un nuevo usuario). Llamamos a esta condici贸n un
requisito (o requisito en ingl茅s). Para comprender si se ha cumplido el requisito, podemos basarnos en un
conjunto de derechos de usuario
actuales :
credenciales . Es decir, es suficiente definir una funci贸n:
function isSatisfied(requirement, credentials) { if (...) { return false; } return true; }
Ahora hemos decidido m谩s o menos la condici贸n de renderizado. 驴C贸mo usarlo?
1. Podemos usar el enfoque de la frente:
const requirement = {...}; class App extends Component { render() { const {credentials} = this.props; return isSatisfied(requirement, credentials) && <TargetComponent>; } }
2. Podemos ir un poco m谩s all谩 y envolver el componente objetivo en otro, que verificar谩 el 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/>; } }
Escribir manualmente un contenedor para cada componente de destino es bastante triste. 驴C贸mo podemos simplificar esto?
3. Podemos recurrir al mecanismo HOC (por analog铆a con connect desde "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> ... ); } }
Ya es mejor, pero sigue siendo miserable: debe lanzar manualmente las credenciales a trav茅s de todo el 谩rbol de componentes. 驴Qu茅 se puede hacer con esto? Es l贸gico suponer que las credenciales del usuario actual son un objeto global para toda la aplicaci贸n. Entonces otra vez react-redux-connect viene al rescate. Despu茅s de leer un art铆culo sobre el dispositivo de este m贸dulo, encontramos que utiliza algunos
contextos ReactJS.
4. Usando el mecanismo de contexto, obtenemos el enfoque 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> ); } }
Ep铆logo
Fue una breve digresi贸n sobre la idea misma. En base a esta idea, se implement贸 un m贸dulo (
github ,
npm ), que proporciona caracter铆sticas m谩s interesantes y es m谩s f谩cil de incrustar (vea README.md en el github y la
demostraci贸n usando el m贸dulo).
Solo por alguna raz贸n no pude obtener el paquete npm creado en la demostraci贸n, as铆 que tuve que insertar el c贸digo del m贸dulo all铆. Pero el m贸dulo instalado a trav茅s de
npm install react-rbac-guard funciona localmente (Chrome 69.0.3497.100). Sospecho que el problema est谩 en el m茅todo de compilaci贸n: acabo de copiar los archivos package.json y webpack.config.prod.js de un
m贸dulo con un prop贸sito similar.
Como no soy un desarrollador front-end, todav铆a hay mucho trabajo sin terminar (falta de pruebas, inoperancia en
https://codesandbox.io y, posiblemente, otros puntos perdidos). Por lo tanto, si hay comentarios, sugerencias o solicitudes de extracci贸n, 隆bienvenido!
PPS: Todos los comentarios relacionados con la ortograf铆a, incluso en README.md, env铆e mensajes personales o en forma de solicitud de extracci贸n.