useState¶
useState
- это хук React, который позволяет вам добавить переменную состояния в ваш компонент.
1 |
|
Описание¶
useState(initialState)
¶
Вызовите useState
на верхнем уровне вашего компонента, чтобы объявить переменную состояния.
1 2 3 4 5 6 7 8 |
|
Принято называть переменные состояния типа [something, setSomething]
, используя деструктуризацию массива.
Параметры¶
initialState
: Значение, которое вы хотите, чтобы состояние было первоначальным. Это может быть значение любого типа, но для функций предусмотрено особое поведение. Этот аргумент игнорируется после первоначального рендеринга.- Если вы передадите функцию в качестве
initialState
, она будет рассматриваться как инициализирующая функция. Она должна быть чистой, не принимать никаких аргументов и возвращать значение любого типа. React будет вызывать вашу функцию инициализатора при инициализации компонента и сохранять возвращаемое значение в качестве начального состояния.
- Если вы передадите функцию в качестве
Возвращаемое значение¶
useState
возвращает массив, содержащий ровно два значения:
- Текущее состояние. Во время первого рендера оно будет соответствовать переданному вами
initialState
. - Функция
set
, которая позволяет обновить состояние до другого значения и вызвать повторный рендеринг.
Предупреждения¶
useState
- это хук, поэтому вы можете вызывать его только на верхнем уровне вашего компонента или ваших собственных хуков. Вы не можете вызывать его внутри циклов или условий. Если вам это нужно, создайте новый компонент и переместите состояние в него.- В строгом режиме React будет вызывать вашу функцию инициализатора дважды, чтобы помочь вам найти случайные примеси. Это поведение только для разработки и не влияет на производство. Если ваша функция инициализатора чиста (как и должно быть), это не должно повлиять на поведение. Результат одного из вызовов будет проигнорирован.
set
функции, такие как setSomething(nextState)
¶
Функция set
, возвращаемая useState
, позволяет обновить состояние до другого значения и вызвать повторный рендеринг. Вы можете передать следующее состояние напрямую или функцию, которая вычисляет его на основе предыдущего состояния:
1 2 3 4 5 6 7 |
|
Параметры¶
nextState
: Значение, которое вы хотите видеть в состоянии. Это может быть значение любого типа, но есть особое поведение для функций.- Если вы передадите функцию в качестве
nextState
, она будет рассматриваться как функция обновления. Она должна быть чистой, принимать состояние ожидания в качестве единственного аргумента и возвращать следующее состояние. React поместит вашу функцию обновления в очередь и перерендерит ваш компонент. Во время следующего рендеринга React вычислит следующее состояние, применив все стоящие в очереди функции обновления к предыдущему состоянию.
- Если вы передадите функцию в качестве
Возврат¶
Функции set
не имеют возвращаемого значения.
Предостережения¶
-
Функция
set
только обновляет переменную состояния для следующего рендера. Если вы прочитаете переменную состояния после вызова функцииset
, вы получите старое значение, которое было на экране до вашего вызова. -
Если новое значение, которое вы предоставили, идентично текущему
состоянию
, что определяется сравнениемObject.is
, React пропустит повторное отображение компонента и его дочерних элементов. Это оптимизация. Хотя в некоторых случаях React может потребоваться вызвать ваш компонент, прежде чем пропустить дочерние компоненты, это не должно повлиять на ваш код. -
React batches state updates. Он обновляет экран после того, как все обработчики событий запущены и вызвали свои функции
set
. Это предотвращает множественные повторные рендеринги во время одного события. В редких случаях, когда вам нужно заставить React обновить экран раньше, например, для доступа к DOM, вы можете использоватьflushSync
. -
Вызов функции
set
во время рендеринга разрешен только внутри текущего рендерингового компонента. React отбросит его вывод и немедленно попытается отрендерить его снова с новым состоянием. Этот паттерн нужен редко, но вы можете использовать его для сохранения информации из предыдущих рендеров. -
В строгом режиме React будет вызывать вашу функцию обновления дважды, чтобы помочь вам найти случайные примеси. Это поведение только для разработчиков и не влияет на производство. Если ваша функция обновления является чистой (как и должно быть), это не должно повлиять на поведение. Результат одного из вызовов будет проигнорирован.
Использование¶
Добавление состояния в компонент¶
Вызовите useState
на верхнем уровне вашего компонента для объявления одной или более переменных состояния.
1 2 3 4 5 6 7 |
|
Принято называть переменные состояния типа [something, setSomething]
, используя деструктуризацию массива.
useState
возвращает массив, содержащий ровно два элемента:
- текущее состояние этой переменной состояния, первоначально установленное в начальное состояние, которое вы указали.
set
функция, которая позволяет вам изменить ее на любое другое значение в ответ на взаимодействие.
Чтобы обновить то, что отображается на экране, вызовите функцию set
с некоторым следующим состоянием:
1 2 3 |
|
React сохранит следующее состояние, снова отобразит ваш компонент с новыми значениями и обновит пользовательский интерфейс.
set
не изменяет текущее состояние
Вызов функции set
не изменяет текущее состояние в уже выполняющемся коде:
1 2 3 4 |
|
Это влияет только на то, что useState
будет возвращать, начиная со следующего рендера.
Базовые примеры useState¶
1. Счетчик (число)
В этом примере переменная состояния count
содержит число. Нажатие на кнопку увеличивает его.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
|
2. Текстовое поле (строка)
В этом примере переменная состояния text
содержит строку. Когда вы вводите текст, handleChange
считывает последнее значение ввода из DOM-элемента ввода браузера и вызывает setText
для обновления состояния. Это позволяет отобразить текущий текст
ниже.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
|
3. Checkbox (boolean)
В этом примере переменная состояния liked
содержит булево значение. Когда вы нажимаете на вход, setLiked
обновляет переменную состояния liked
, чтобы узнать, установлен ли флажок в чекбоксе браузера. Переменная liked
используется для вывода текста под чекбоксом.
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 |
|
4. Форма (две переменные)
Вы можете объявить более одной переменной состояния в одном компоненте. Каждая переменная состояния полностью независима.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
Обновление состояния на основе предыдущего состояния¶
Предположим, что age
равен 42
. Этот обработчик вызывает setAge(age + 1)
три раза:
1 2 3 4 5 |
|
Однако после одного клика age
будет только 43
, а не 45
! Это происходит потому, что вызов функции set
не обновляет переменную состояния age
в уже запущенном коде. Поэтому каждый вызов setAge(age + 1)
становится setAge(43)
.
Чтобы решить эту проблему, вы можете передать функцию updater в setAge
вместо следующего состояния:
1 2 3 4 5 |
|
Здесь a => a + 1
- это ваша функция обновления. Она берет отложенное состояние и вычисляет следующее состояние из него.
React помещает ваши функции обновления в очередь Затем, во время следующего рендеринга, он будет вызывать их в том же порядке:
a => a + 1
получит42
в качестве ожидающего состояния и вернет43
в качестве следующего состояния.a => a + 1
получит43
как ожидающее состояние и вернет44
как следующее состояние.a => a + 1
получит44
как состояние ожидания и вернет45
как следующее состояние.
Других обновлений в очереди нет, поэтому в конце React сохранит 45
как текущее состояние.
По традиции принято называть аргумент ожидающего состояния по первой букве имени переменной состояния, например a
для age
. Однако вы также можете назвать его prevAge
или как-то иначе, что вам кажется более понятным.
React может вызывать ваши апдейтеры дважды в процессе разработки, чтобы убедиться, что они чистые.
Всегда ли использование программы обновления предпочтительнее?
Вы можете услышать рекомендацию всегда писать код типа setAge(a => a + 1)
, если состояние, которое вы устанавливаете, вычисляется из предыдущего состояния. В этом нет ничего плохого, но это также не всегда необходимо.
В большинстве случаев между этими двумя подходами нет никакой разницы. React всегда гарантирует, что для намеренных действий пользователя, таких как щелчки, переменная состояния age
будет обновлена перед следующим щелчком. Это означает, что нет риска того, что обработчик щелчка увидит "устаревший" age
в начале обработчика события.
Однако, если вы делаете несколько обновлений в рамках одного события, обновляющие устройства могут быть полезны. Они также полезны, если доступ к самой переменной состояния неудобен (вы можете столкнуться с этим при оптимизации повторных рендеров).
Если вы предпочитаете последовательность, а не более многословный синтаксис, разумно всегда писать обновляющее устройство, если состояние, которое вы устанавливаете, вычисляется из предыдущего состояния. Если оно вычисляется из предыдущего состояния некоторой другой переменной состояния, вы можете объединить их в один объект и использовать редуктор.
Разница между передачей функции обновления и передачей следующего состояния напрямую¶
1. Передача функции обновления
В этом примере передается функция обновления, поэтому кнопка "+3" работает.
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 |
|
2. Передача следующего состояния напрямую
В этом примере не передается функция обновления, поэтому кнопка "+3" не работает так, как задумано.
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 |
|
Обновление объектов и массивов в состоянии¶
Вы можете помещать объекты и массивы в состояние. В React состояние считается доступным только для чтения, поэтому вам следует заменить его, а не изменять существующие объекты. Например, если у вас есть объект form
в состоянии, не изменяйте его:
1 2 |
|
Вместо этого замените весь объект, создав новый:
1 2 3 4 5 |
|
Прочитайте обновление объектов в состоянии и обновление массивов в состоянии, чтобы узнать больше.
Примеры объектов и массивов в состоянии¶
1. Форма (объект)
В этом примере переменная состояния form
содержит объект. Каждый вход имеет обработчик изменений, который вызывает setForm
со следующим состоянием всей формы. Синтаксис распространения { ...form }
гарантирует, что объект состояния заменяется, а не изменяется.
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. Форма (вложенный объект)
В этом примере состояние более вложенное. Когда вы обновляете вложенное состояние, вам необходимо создать копию объекта, который вы обновляете, а также любых объектов, "содержащих" его на пути вверх. Прочитайте обновление вложенного объекта, чтобы узнать больше.
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 82 83 84 85 86 87 88 89 90 91 92 93 |
|
3. Список (массив)
В этом примере переменная состояния todos
содержит массив. Каждый обработчик кнопки вызывает setTodos
со следующей версией этого массива. Синтаксис распространения [...todos]
, todos.map()
и todos.filter()
гарантируют, что массив состояний заменяется, а не изменяется.
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 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
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 |
|
4. Написание краткой логики обновления с помощью 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 |
|
Избегание воссоздания начального состояния¶
React сохраняет начальное состояние один раз и игнорирует его при последующих рендерах.
1 2 3 4 5 6 |
|
Хотя результат createInitialTodos()
используется только для начального рендеринга, вы все равно вызываете эту функцию при каждом рендеринге. Это может быть расточительно, если она создает большие массивы или выполняет дорогие вычисления.
Чтобы решить эту проблему, вы можете передать ее в качестве функции инициализатора в useState
вместо этого:
1 2 3 4 |
|
Обратите внимание, что вы передаете createInitialTodos
, которая является самой функцией, а не createInitialTodos()
, которая является результатом ее вызова. Если вы передадите функцию в useState
, React будет вызывать ее только во время инициализации.
React может вызвать ваши инициализаторы дважды в процессе разработки, чтобы убедиться, что они чистые.
Разница между передачей инициализатора и передачей начального состояния напрямую¶
1. Передача функции инициализатора
Этот пример передает функцию инициализатора, поэтому функция createInitialTodos
выполняется только во время инициализации. Она не выполняется при повторном рендеринге компонента, например, когда вы вводите текст в поле ввода.
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 |
|
2. Передача начального состояния напрямую
В этом примере не передается функция инициализатора, поэтому функция createInitialTodos
запускается при каждом рендере, например, когда вы вводите текст в 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 |
|
Сброс состояния с помощью ключа¶
Вы часто сталкиваетесь с атрибутом key
при рендеринге списков. Однако он также служит для другой цели.
Вы можете сбросить состояние компонента, передав компоненту другой key
. В этом примере кнопка Reset изменяет переменную состояния version
, которую мы передаем в качестве key
в Form
. Когда key
меняется, React заново создает компонент Form
(и все его дочерние элементы) с нуля, поэтому его состояние сбрасывается.
Прочитайте сохранение и сброс состояния, чтобы узнать больше.
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 |
|
Хранение информации из предыдущих рендеров¶
Обычно вы обновляете состояние в обработчиках событий. Однако в редких случаях вы можете захотеть изменить состояние в ответ на рендеринг - например, вы можете захотеть изменить переменную состояния при изменении пропса.
В большинстве случаев это не нужно:
- Если нужное вам значение может быть вычислено полностью из текущего пропса или другого состояния, удалите лишнее состояние вообще Если вас беспокоит слишком частое повторное вычисление, вам поможет хук
useMemo
. - Если вы хотите сбросить состояние всего дерева компонентов, передайте своему компоненту другой
ключ
. - Если можете, обновите все соответствующие состояния в обработчиках событий.
В редких случаях, когда ни один из этих способов не применим, существует шаблон, который можно использовать для обновления состояния на основе значений, которые были отображены на данный момент, путем вызова функции set
во время рендеринга компонента.
Вот пример. Этот компонент CountLabel
отображает переданный ему параметр count
:
1 2 3 |
|
Допустим, вы хотите показать, увеличился или уменьшился счетчик с момента последнего изменения. Пропс count
не говорит вам об этом - вам нужно отслеживать его предыдущее значение. Добавьте переменную состояния prevCount
, чтобы отслеживать его. Добавьте еще одну переменную состояния trend
, чтобы отслеживать, увеличился или уменьшился счетчик. Сравните prevCount
с count
, и если они не равны, обновите и prevCount
, и trend
. Теперь вы можете показать как текущее значение счетчика, так и как оно изменилось с момента последнего рендера.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
|
Обратите внимание, что если вы вызываете функцию set
во время рендеринга, она должна быть внутри условия типа prevCount !== count
, а внутри условия должен быть вызов типа setPrevCount(count)
. В противном случае ваш компонент будет перерисовываться в цикле до тех пор, пока не произойдет сбой. Кроме того, вы можете обновить состояние текущего рендеринга компонента только таким образом. Вызов функции set
другого компонента во время рендеринга является ошибкой. Наконец, ваш вызов set
должен обновлять состояние без мутации - это не означает, что вы можете нарушать другие правила чистых функций.
Этот паттерн может быть трудным для понимания, и обычно его лучше избегать. Однако это лучше, чем обновлять состояние в эффекте. Когда вы вызываете функцию set
во время рендеринга, React перерендерит этот компонент сразу после того, как ваш компонент выйдет с оператором return
, и до рендеринга дочерних компонентов. Таким образом, дочерние компоненты не нужно рендерить дважды. Оставшаяся часть функции вашего компонента будет по-прежнему выполняться (а результат будет отброшен). Если ваше условие находится ниже всех вызовов Hook, вы можете добавить ранний return;
, чтобы перезапустить рендеринг раньше.
Устранение неполадок¶
Я обновил состояние, но логирование выдает мне старое значение¶
Вызов функции set
не изменяет состояние в работающем коде:
1 2 3 4 5 6 7 8 9 10 |
|
Это происходит потому, что состояние ведет себя как моментальный снимок. Обновление состояния запрашивает другой рендер с новым значением состояния, но не влияет на переменную JavaScript count
в уже запущенном обработчике событий.
Если вам нужно использовать следующее состояние, вы можете сохранить его в переменной перед передачей в функцию set
:
1 2 3 4 5 |
|
Я обновил состояние, но экран не обновляется¶
React будет игнорировать ваше обновление, если следующее состояние равно предыдущему, что определяется сравнением Object.is
. Обычно это происходит, когда вы изменяете объект или массив в состоянии напрямую:
1 2 |
|
Вы изменили существующий объект obj
и передали его обратно в setObj
, поэтому React проигнорировал обновление. Чтобы исправить это, вам нужно убедиться, что вы всегда заменяете объекты и массивы в состоянии вместо того, чтобы мутировать их:
1 2 3 4 5 |
|
Я получаю ошибку: "Слишком много повторных рендеров"¶
Вы можете получить ошибку, которая гласит: 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, чтобы найти конкретный вызов функции set
, ответственный за ошибку.
Моя функция инициализатора или обновления выполняется дважды¶
В Строгом режиме React будет вызывать некоторые из ваших функций дважды вместо одного раза:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
|
Это ожидаемо и не должно нарушать ваш код.
Это поведение только для разработчиков помогает вам поддерживать чистоту компонентов. React использует результат одного из вызовов и игнорирует результат другого вызова. Пока ваши функции компонентов, инициализаторов и обновлений чисты, это не должно влиять на вашу логику. Однако если они случайно оказались нечистыми, это поможет вам заметить ошибки.
Например, эта нечистая функция обновления мутирует массив в состоянии:
1 2 3 4 |
|
Поскольку React дважды вызывает вашу функцию обновления, вы увидите, что todo было добавлено дважды, поэтому вы будете знать, что произошла ошибка. В этом примере вы можете исправить ошибку, заменив массив вместо его мутации:
1 2 3 4 |
|
Теперь, когда эта функция обновления является чистой, ее повторный вызов не изменит поведение. Вот почему двойной вызов React помогает найти ошибки. Только функции компонентов, инициализаторов и обновлений должны быть чистыми. Обработчики событий не должны быть чистыми, поэтому React никогда не будет вызывать ваши обработчики событий дважды.
Для получения дополнительной информации читайте keeping components pure.
Я пытаюсь установить состояние на функцию, но вместо этого вызывается она¶
Вы не можете поместить функцию в состояние таким образом:
1 2 3 4 5 |
|
Поскольку вы передаете функцию, React предполагает, что someFunction
является инициализирующей функцией, а someOtherFunction
является обновляющей функцией, поэтому он пытается вызвать их и сохранить результат. Чтобы действительно сохранить функцию, вы должны поставить () =>
перед ними в обоих случаях. Тогда React будет хранить переданные вами функции.
1 2 3 4 5 |
|
Источник — https://react.dev/reference/react/useState