- PVSM.RU - https://www.pvsm.ru -
Привет, дорогой читатель!
Некоторое время (около года) назад столкнулся с необходимостью условной отрисовки компонентов в ReactJS в зависимости от текущих прав пользователя. Первым делом начал искать готовые решения и «лучшие практики». Статья "Role based authorization in React [1]" произвела больше всего впечатления своим использованием Higher-Order Components [2] (HOC). Но, к сожалению, решения, которое меня удовлетворяет, не нашел.
В то время был немного знаком с «react-redux-connect» (npm-модуль), и меня сильно зацепил подход с декорированием, который используется в функции connect. Подробный разбор устройства connect можно найти тут [4].
Для начала надо определить какая минимальная информация нужна для принятия решения об отрисовке компонента. Очевидно, для отрисовки необходимо выполнение некоторого условия (как вариант наличие какого-то права — например, право добавления нового пользователя). Назовем это условие требованием (или requirement на английском). Понять, было ли требование удовлетворено, можем на основе набора текущих прав пользователя — credentials. То есть достаточно определить функцию:
function isSatisfied(requirement, credentials) {
if (...) {
return false;
}
return true;
}
Теперь мы более или менее определились с условием отрисовки. А как это использовать?
1. Можем использовать подход в лоб:
const requirement = {...};
class App extends Component {
render() {
const {credentials} = this.props;
return isSatisfied(requirement, credentials) && <TargetComponent>;
}
}
2. Можем пойти чуть дальше, и обернуть целевой компонент в другой, который и будет делать проверку выполнения требования:
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/>;
}
}
Вручную писать обертку для каждого целевого компонента довольно муторно. Как это можем упростить?
3. Можем прибегнуть к механизму HOC (по аналогии с connect из «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>
...
);
}
}
Уже лучше, но всё еще убого — нужно руками пробрасывать credentials через всё дерево компонентов. Что с этим можно сделать? Логично предположить, что credentials текущего пользователя — это глобальный объект для всего приложения. Тогда на помощь снова приходит «react-redux-connect». Почитав статью об устройстве этого модуля, обнаруживаем, что в нём используются некие контексты [5] ReactJS.
4. С использованием механизма контекстов получаем окончательный подход:
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>
);
}
}
Это был краткий экскурс в саму идею. На базе этой идеи был реализован модуль (github [6], npm [7]), который предоставляет более интересные возможности и его проще встроить (см. README.md в гитхабе и демо [8] с использованием модуля).
Только мне почему-то не удалось завести созданный npm пакет в демо, поэтому пришлось туда вставлять сам код модуля. Но модуль, установленный через npm install react-rbac-guard, локально работает (Chrome 69.0.3497.100). Подозреваю, что проблема в способе сборки — я просто скопировал файлы package.json и webpack.config.prod.js из модуля [9] с аналогичным предназначением.
Так как я не являюсь фронтенд разработчиком, ещё много чего недоделанного (отсутствие тестов, неработоспособность в https://codesandbox.io [10] и, возможно, другие упущенные моменты). Поэтому, если будут замечания, предложения или пулл-реквесты, то добро пожаловать!
P.P.S.: Все замечания по поводу правописания, в том числе в README.md, просьба присылать в личные сообщения или в виде пулл-реквеста.
Автор: Nurzhan69
Источник [11]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/javascript/297425
Ссылки в тексте:
[1] Role based authorization in React: https://hackernoon.com/role-based-authorization-in-react-c70bb7641db4
[2] Higher-Order Components: https://reactjs.org/docs/higher-order-components.html
[3] ответ в stackoverflow: https://stackoverflow.com/questions/42567575/hide-some-react-component-children-depending-on-user-role/47579290#47579290
[4] тут: https://medium.com/devschacht/jakob-lind-code-your-own-redux-part-2-the-connect-function-d941dc247c58
[5] контексты: https://reactjs.org/docs/context.html
[6] github: https://github.com/nurzhan-saktaganov/react-rbac-guard
[7] npm: https://www.npmjs.com/package/react-rbac-guard
[8] демо: https://codesandbox.io/s/znmxlw59jm
[9] модуля: https://github.com/brainhubeu/react-permissible
[10] https://codesandbox.io: https://codesandbox.io
[11] Источник: https://habr.com/post/428143/?utm_campaign=428143
Нажмите здесь для печати.