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