useReducer¶
useReducer
- это хук React, который позволяет добавить редьюсер в ваш компонент.
1 |
|
Описание¶
useReducer(reducer, initialArg, init?)
¶
Вызовите useReducer
на верхнем уровне вашего компонента для управления его состоянием с помощью редьюсера.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Параметры¶
reducer
: Функция reducer, определяющая, как обновляется состояние. Она должна быть чистой, принимать в качестве аргументов состояние и действие и возвращать следующее состояние. Состояние и действие могут быть любого типа.initialArg
: Значение, из которого вычисляется начальное состояние. Это может быть значение любого типа. То, как из него будет вычисляться начальное состояние, зависит от следующего аргументаinit
.- опционально
init
: Функция инициализатора, которая должна вернуть начальное состояние. Если она не указана, то начальное состояние устанавливается вinitialArg
. В противном случае начальное состояние устанавливается в результат вызоваinit(initialArg)
.
Возвращаемое значение¶
useReducer
возвращает массив, содержащий ровно два значения:
- Текущее состояние. Во время первого рендера оно устанавливается в
init(initialArg)
илиinitialArg
(если нетinit
). - Функция
dispatch
, которая позволяет обновить состояние до другого значения и вызвать повторный рендеринг.
Предостережения¶
useReducer
- это хук, поэтому вы можете вызывать его только на верхнем уровне вашего компонента или ваших собственных хуков. Вы не можете вызывать его внутри циклов или условий. Если вам это нужно, создайте новый компонент и переместите состояние в него.- В строгом режиме React будет вызывать ваш редуктор и инициализатор дважды, чтобы помочь вам найти случайные примеси Это поведение только для разработки и не влияет на производство. Если ваши редуктор и инициализатор чисты (как и должно быть), это не должно повлиять на вашу логику. Результат одного из вызовов игнорируется.
dispatch
функция¶
Функция dispatch
, возвращаемая useReducer
, позволяет вам обновить состояние до другого значения и вызвать повторный рендеринг. В качестве единственного аргумента в функцию dispatch
нужно передать действие:
1 2 3 4 5 6 |
|
React установит следующее состояние как результат вызова функции reducer
, которую вы предоставили с текущим состоянием
и действием, которое вы передали в dispatch
.
Параметры¶
действие
: Действие, выполняемое пользователем. Это может быть значение любого типа. По соглашению, действие обычно представляет собой объект со свойствомtype
, идентифицирующим его, и, опционально, другими свойствами с дополнительной информацией.
Возврат¶
Функции dispatch
не имеют возвращаемого значения.
Предостережения¶
- Функция
dispatch
только обновляет переменную состояния для следующего рендера. Если вы прочитаете переменную состояния после вызова функцииdispatch
, вы получите старое значение, которое было на экране до вашего вызова. - Если новое значение, которое вы предоставили, идентично текущему
state
, что определяется сравнениемObject.is
, React пропустит повторное отображение компонента и его дочерних элементов. Это оптимизация. React все еще может потребоваться вызвать ваш компонент перед игнорированием результата, но это не должно повлиять на ваш код. - React batches state updates. Он обновляет экран после того, как все обработчики событий запущены и вызвали свои функции
set
. Это предотвращает множественные повторные рендеринги во время одного события. В редких случаях, когда вам нужно заставить React обновить экран раньше, например, для доступа к DOM, вы можете использоватьflushSync
.
Использование¶
Добавление редуктора к компоненту¶
Вызовите useReducer
на верхнем уровне вашего компонента для управления состоянием с помощью reducer.
1 2 3 4 5 6 7 8 9 10 11 12 |
|
useReducer
возвращает массив, содержащий ровно два элемента:
- текущее состояние этой переменной состояния, первоначально установленное в начальное состояние, которое вы указали.
dispatch
функцию, которая позволяет вам изменять состояние в ответ на взаимодействие.
Чтобы обновить то, что отображается на экране, вызовите dispatch
с объектом, представляющим действия пользователя, называемым action:
1 2 3 |
|
React передаст текущее состояние и действие вашей функции редуктора. Ваш редуктор вычислит и вернет следующее состояние. React будет хранить это следующее состояние, рендерить ваш компонент с ним и обновлять пользовательский интерфейс.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
|
useReducer
очень похож на useState
, но он позволяет вам перенести логику обновления состояния из обработчиков событий в одну функцию вне вашего компонента. Подробнее о выбор между useState
и useReducer
Написание функции редуктора¶
Функция редуктора объявляется следующим образом:
1 2 3 |
|
Затем вам нужно заполнить код, который будет вычислять и возвращать следующее состояние. По традиции, это принято записывать как оператор switch
. Для каждого case
в switch
, вычислите и верните некоторое следующее состояние.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
Действия могут иметь любую форму. По традиции принято передавать объекты со свойством type
, идентифицирующим действие. Он должен включать минимально необходимую информацию, которая нужна редуктору для вычисления следующего состояния.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Имена типов действий являются локальными для вашего компонента. Каждое действие описывает одно взаимодействие, даже если оно приводит к нескольким изменениям данных. Форма состояния произвольна, но обычно это объект или массив.
Прочитайте извлечение логики состояния в редуктор, чтобы узнать больше.
Обновление состояния
Состояние доступно только для чтения. Не изменяйте никакие объекты или массивы в состоянии:
1 2 3 4 5 6 7 8 9 |
|
Вместо этого всегда возвращайте новые объекты из вашего редуктора:
1 2 3 4 5 6 7 8 9 10 11 |
|
Прочитайте обновление объектов в состоянии и обновление массивов в состоянии, чтобы узнать больше.
Основные примеры использованияReducer¶
1. Форма (объект)
В этом примере редуктор управляет объектом state с двумя полями: name
и age
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
|
2. Todo list (массив)
В этом примере редуктор управляет массивом задач. Массив необходимо обновить без мутации.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 |
|
Написание краткой логики обновления с помощью Immer
Если обновление массивов и объектов без мутации кажется утомительным, вы можете использовать библиотеку типа Immer для сокращения повторяющегося кода. Immer позволяет вам писать лаконичный код, как если бы вы мутировали объекты, но под капотом она выполняет неизменяемые обновления:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 |
|
Избегание воссоздания начального состояния¶
React сохраняет начальное состояние один раз и игнорирует его при последующих рендерах.
1 2 3 4 5 6 7 8 9 10 11 |
|
Хотя результат createInitialState(username)
используется только для начального рендеринга, вы все равно вызываете эту функцию при каждом рендеринге. Это может быть расточительно, если она создает большие массивы или выполняет дорогие вычисления.
Чтобы решить эту проблему, вы можете передать ее в качестве инициализатора функции в useReducer
в качестве третьего аргумента вместо этого:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Обратите внимание, что вы передаете createInitialState
, которая является самой функцией, а не createInitialState()
, которая является результатом ее вызова. Таким образом, начальное состояние не создается заново после инициализации.
В приведенном выше примере createInitialState
принимает аргумент username
. Если вашему инициализатору не нужна информация для вычисления начального состояния, вы можете передать null
в качестве второго аргумента в useReducer
.
Разница между передачей инициализатора и передачей начального состояния напрямую¶
1. Передача функции инициализатора
В этом примере передается функция инициализатора, поэтому функция createInitialState
выполняется только во время инициализации. Она не выполняется при повторном рендеринге компонента, например, когда вы вводите текст в поле ввода.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
|
2. Передача начального состояния напрямую
В этом примере не передается функция инициализатора, поэтому функция createInitialState
запускается при каждом рендере, например, когда вы вводите текст в input
. Разница в поведении не заметна, но этот код менее эффективен.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 |
|
Устранение неполадок¶
Я диспетчеризировал действие, но логирование дает мне старое значение состояния¶
Вызов функции dispatch
не изменяет состояние в работающем коде:
1 2 3 4 5 6 7 8 9 10 |
|
Это происходит потому, что состояние ведет себя как моментальный снимок. Обновление состояния запрашивает другой рендер с новым значением состояния, но не влияет на переменную JavaScript state
в уже запущенном обработчике событий.
Если вам нужно узнать следующее значение состояния, вы можете вычислить его вручную, вызвав редьюсер самостоятельно:
1 2 3 4 5 6 |
|
Я запустил экшн, но экран не обновляется¶
React будет игнорировать ваше обновление, если следующее состояние равно предыдущему, что определяется сравнением Object.is
. Обычно это происходит, когда вы изменяете объект или массив в состоянии напрямую:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Вы изменили существующий объект state
и вернули его, поэтому React проигнорировал обновление. Чтобы исправить это, вам нужно убедиться, что вы всегда обновляете объекты в состоянии и обновляете массивы в состоянии, а не мутируете их:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
Часть состояния моего редуктора становится неопределенной после диспетчеризации¶
Убедитесь, что каждая ветвь case
копирует все существующие поля при возврате нового состояния:
1 2 3 4 5 6 7 8 9 10 11 |
|
Без ...state
выше, возвращаемое следующее состояние будет содержать только поле age
и ничего больше.
Все состояние моего редуктора становится неопределенным после диспетчеризации¶
Если ваше состояние неожиданно становится undefined
, скорее всего, вы забыли return
состояние в одном из случаев, или ваш тип действия не соответствует ни одному из утверждений case
. Чтобы выяснить причину, выбросьте ошибку вне switch
:
1 2 3 4 5 6 7 8 9 10 11 |
|
Вы также можете использовать статическую проверку типов, например, TypeScript, для выявления таких ошибок.
Я получаю ошибку: "Too many re-renders"¶
Вы можете получить ошибку, которая гласит: Too many re-renders. React limits the number of renders to prevent an infinite loop.
Обычно это означает, что вы безоговорочно отправляете действие во время рендера, поэтому ваш компонент попадает в цикл: рендер, диспетчеризация (которая вызывает рендер), рендер, диспетчеризация (которая вызывает рендер) и так далее. Очень часто причиной этого является ошибка в определении обработчика события:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
Если вы не можете найти причину этой ошибки, нажмите на стрелку рядом с ошибкой в консоли и просмотрите стек JavaScript, чтобы найти конкретный вызов функции dispatch
, ответственный за ошибку.
Моя функция редуктора или инициализатора выполняется дважды¶
В Строгом режиме React будет вызывать ваши функции редуктора и инициализатора дважды. Это не должно сломать ваш код.
Это поведение только для разработчиков помогает вам поддерживать чистоту компонентов. React использует результат одного из вызовов и игнорирует результат другого вызова. Пока ваши функции компонента, инициализатора и редуктора чисты, это не должно влиять на вашу логику. Однако если они случайно оказались нечистыми, это поможет вам заметить ошибки.
Например, эта нечистая функция reducer мутирует массив в состоянии:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
|
Поскольку React дважды вызывает вашу функцию reducer
, вы увидите, что todo
был добавлен дважды, поэтому вы будете знать, что произошла ошибка. В этом примере вы можете исправить ошибку, заменив массив вместо его мутирования:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
Теперь, когда эта функция reducer является чистой, ее повторный вызов не изменит поведение. Вот почему двойной вызов React помогает найти ошибки. Только функции компонента, инициализатора и редуктора должны быть чистыми. Обработчики событий не должны быть чистыми, поэтому React никогда не будет вызывать обработчики событий дважды.
Прочитайте поддержание чистоты компонентов, чтобы узнать больше.
Источник — https://react.dev/reference/react/useReducer