Создание многоразового состояния в React
Впервые я услышал о State Reducer от Кента К. Доддса в его статье The State Reducer Pattern. С помощью этого шаблона вы можете заставить компонент переопределить изменения состояния, которые дочерний компонент хочет выполнить.
Давайте рассмотрим обычный пример Toggle, который Кент показал в своей презентации:
Вариант использования
- Повторно использовать уже написанные компоненты
- Не нарушайте существующие API
Наш сценарий выглядит следующим образом: мы только что получили запрос функции сделать так, чтобы 1 из тумблеров на нашей странице мог быть активирован только 4 раза. Остальные наши тумблеры должны оставаться неизменными - их можно переключать столько раз, сколько пожелает пользователь.
Обычно мы выполняем управляемый компонент, и родительский компонент переключателя отслеживает время его нажатия и состояние кнопки. Но мы стремимся к приключениям и не хотим менять компоненты Switch и Toggle, которых у нас уже слишком много.
Наш компонент Switch - это простой компонент React без сохранения состояния, который не делает никаких предположений о том, как on
отслеживается или что делает onToggle
:
И у нас есть компонент Toggle render-prop, который отслеживает состояние on
и то, как управлять переключением:
Что нам нужно сделать, так это как-то обновить Toggle.js
, чтобы мы могли переопределить состояние on
без использования контролируемых свойств. Мы также хотим, чтобы это не было критическим изменением, чтобы все остальные переключатели и переключатели, которые у нас есть, продолжали работать.
Вот типичный пример из нашего гипотетического приложения использования Toggle and Switch. Некоторые переключатели не находятся в рендере Toggle и имеют свою сумасшедшую бизнес-логику, которую мы тоже не хотим трогать:
Наше первоначальное решение
- Используйте состояние для отслеживания
clickCount
- Не звоните
onToggle
, еслиclickCount
равно 4 или больше.
По мере того, как мы работаем над реализацией нашей функции, мы полагаем, что компонент, который использует компоненты Toggle + Switch, должен будет отслеживать, сколько раз была нажата кнопка. И мы также предполагаем, что можем просто изменить значение onToggle
prop, чтобы использовать функцию, которая не будет переключаться, если clickCount
равно или больше 4.
Это заставило меня скривиться: наш код, отвечающий новым бизнес-требованиям, занимает весь наш компонент, и теперь у нас есть бизнес-логика в двух методах и в нашей onToggle
функции.
В другом месте нашего проекта мы используем redux и с любовью и любовью думаем о том, насколько проста структура наших редукторов. Мы видим, что вся логика, связанная с загрузкой, хранением и манипулированием этими данными, централизована в наших редукторах. По большей части наши редукторы содержат бизнес-логику, касающуюся перехода к данным.
Посмотрим, сможем ли мы применить тот же шаблон к нашей задаче.
Редукторы Redux
- Повторно примените идеи из redux
- Определите наше следующее состояние на основе нашего предыдущего состояния
Мы хотим локализовать небольшой редуктор redux для нашего компонента и заставить его отслеживать количество включений переключателя; и, когда это значение равно или превышает 4, прекратить включение переключателя.
Если бы мы написали простой редуктор, он мог бы выглядеть примерно так:
У нас есть полный контроль над тем, изменять или нет состояние on
, и мы можем сохранить активированный count
в тандеме.
Если мы попытаемся применить это к нашим Toggle и Switch, мы увидим две незначительные проблемы: наш Toggle не отправляет действия, и мы не хотим на самом деле использовать redux.
Однако наш компонент Toggle использует setState
.
Поскольку setState
- это просто патч текущего состояния, мы могли бы изменить наш редуктор, чтобы вместо этого он принимал аргументы prevState
и nextState
, а наш редуктор просто возвращает объединенное состояние.
Шаблон редуктора состояния
- Упростите наш редуктор, чтобы объединить
prevState
и предлагаемое состояние - Добавьте редуктор состояния в наш компонент Toggle
Давайте добавим stateReducer
к нашему компоненту и передадим этот редуктор нашему компоненту Toggle. Наш stateReducer
согласовывает данные ему prevState
и nextState
, сохраняя count
и определяя, можно ли включить или выключить переключатель.
Нам нужно будет обновить наш компонент Toggle, чтобы он действительно использовал stateReducer
, и нам нужно сделать это без каких-либо критических изменений.
Вместо прямого вызова setState
, как мы делали раньше, мы вызовем метод для обработки изменений состояния за нас. Мы также дадим предлагаемым изменениям тип, чтобы stateReducer
провайдеры могли легко обрабатывать изменения состояния:
this.internalSetState(state => ({ on: !state.on, type: this.constructor.TOGGLE, }));
И теперь все, что нам нужно сделать, это написать метод interalSetState
, который я с радостью повторно использую у Кента С. Доддса:
Заключение
Шаблон «Редуктор состояния» хорошо применим к ситуациям, когда родительский компонент может захотеть подключиться к состоянию дочернего компонента. Добавляя небольшую сложность к вашему компоненту, вы получаете преимущества, позволяющие повторно использовать этот компонент в критических ситуациях.
Этот шаблон можно использовать для переопределения поведения многократно используемых компонентов, таких как входные данные для поиска, заголовки, элементы управления формами и т. Д.
Иван Монтиэль - основатель и генеральный директор Clarity Hub - компании, которая интегрируется с Intercom, чтобы в режиме реального времени давать командам по работе с клиентами предложения. Он также является архитектором пользовательского интерфейса в Nextiva.