Создание многоразового состояния в 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.

Twitter · Github · LinkedIn