useState
useState — это React-хук, который позволяет добавить переменную состояния в ваш компонент.
const [state, setState] = useState(initialState)
Справочник
useState(initialState)
Вызовите useState в верхнем уровне вашего компонента, чтобы объявить переменную состояния.
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(28);
const [name, setName] = useState('Taylor');
const [todos, setTodos] = useState(() => createTodos());
// ... Обычно переменные состояния именуются, например, [something, setSomething], используя деструктурирующее присваивание.
См. дополнительные примеры ниже.
Параметры
-
initialState: Начальное значение состояния. Оно может быть любого типа, но для функций есть специальное поведение. Этот аргумент игнорируется после начального рендера.- Если вы передадите функцию в качестве
initialState, она будет обработана как функция инициализации. Она должна быть чистой, не принимать аргументы и возвращать значение любого типа. React вызовет вашу функцию инициализации при инициализации компонента и сохранит её возвращаемое значение в качестве начального состояния. См. пример ниже.
- Если вы передадите функцию в качестве
Возвращаемое значение
useState возвращает массив ровно из двух значений:
- Текущее состояние. Во время первого рендера оно будет соответствовать
initialState, которое вы передали. - Функция
set, которая позволяет обновить состояние до другого значения и вызвать перерендеринг.
Примечания
-
useState— это хук, поэтому вы можете вызывать его только на верхнем уровне компонента или в своих собственных хуках. Вы не можете вызывать его внутри циклов или условных операторов. Если это необходимо, выделите новый компонент и переместите состояние в него. - В режиме «Строго» React вызовет вашу функцию инициализации дважды, чтобы помочь вам найти случайные ошибки. Это поведение только для разработки и не влияет на производство. Если ваша функция инициализации чистая (как должно быть), это не должно повлиять на поведение. Результат одного из вызовов будет проигнорирован.
set функции, например, setSomething(nextState)
Функция set , возвращаемая useState , позволяет обновить состояние до другого значения и вызвать перерендеринг. Вы можете передать следующее состояние напрямую или функцию, которая вычисляет его из предыдущего состояния:
const [name, setName] = useState('Edward');
function handleClick() {
setName('Taylor');
setAge(a => a + 1);
// ... Параметры
-
nextState: Значение, которое вы хотите установить в состояние. Оно может быть любого типа, но для функций есть специальное поведение.- Если вы передадите функцию в качестве
nextState, она будет обработана как функция обновления. Она должна быть чистой, принимать текущее состояние в качестве единственного аргумента и возвращать следующее состояние. React поместит вашу функцию обновления в очередь и перерендерит ваш компонент. Во время следующего рендера React рассчитает следующее состояние, применяя все функции обновления из очереди к предыдущему состоянию. См. пример ниже.
- Если вы передадите функцию в качестве
Возвращаемое значение
Функции set не возвращают значения.
Примечания
-
Функция
setобновляет переменную состояния только для следующего рендера. Если вы считываете переменную состояния после вызова функцииset, вы по-прежнему получите старое значение, которое было на экране до вашего вызова. -
Если новое значение совпадает с текущим
state, как определяется сравнениемObject.is, React пропустит перерендеринг компонента и его дочерних элементов. Это оптимизация. Хотя в некоторых случаях React может все равно вызвать ваш компонент перед пропуском дочерних элементов, это не должно влиять на ваш код. -
React группирует обновления состояния. Он обновляет экран после выполнения всех обработчиков событий и вызова их функций
set. Это предотвращает множественные перерендеринги во время одного события. В редких случаях, когда вам нужно заставить React обновить экран раньше, например, для доступа к DOM, вы можете использоватьflushSync. -
Функция
setимеет стабильную идентичность, поэтому вы часто видите её исключение из зависимостей Effect, но включение её не заставит Effect срабатывать. Если линтер позволяет исключить зависимость без ошибок, это безопасно. Узнайте больше об удалении зависимостей Effect. -
Вызов функции
setво время рендеринга разрешён только из компонента, который в данный момент рендерится. React отбросит свой вывод и сразу же попытается перерендерить его снова с новым состоянием. Этот паттерн редко нужен, но вы можете использовать его, чтобы сохранять информацию из предыдущих рендеров. См. пример ниже. -
В режиме «Строго» React вызовет вашу функцию обновления дважды, чтобы помочь вам найти случайные ошибки. Это поведение только для разработки и не влияет на производство. Если ваша функция обновления чистая (как должно быть), это не должно повлиять на поведение. Результат одного из вызовов будет проигнорирован.
Использование
Добавление состояния в компонент
Вызовите useState в верхнем уровне компонента, чтобы объявить одну или несколько переменных состояния.
import { useState } from 'react';
function MyComponent() {
const [age, setAge] = useState(42);
const [name, setName] = useState('Taylor');
// ... Обычно переменные состояния именуются, например, [something, setSomething], используя деструктурирующее присваивание.
useState возвращает массив ровно из двух элементов:
- Текущее состояние этой переменной состояния, изначально установленное в предоставленное вами начальное состояние.
- Функция
set, которая позволяет изменить его на любое другое значение в ответ на взаимодействие.
Чтобы обновить то, что отображается на экране, вызовите функцию set со следующим состоянием:
function handleClick() {
setName('Robin');
} React сохранит следующее состояние, повторно отобразит ваш компонент с новыми значениями и обновит интерфейс.
Опасность
Вызов функции set не изменяет текущее состояние в уже выполняемом коде:
function handleClick() {
setName('Robin');
console.log(name); // Still "Taylor"!
}
Это влияет только на то, что useState вернёт начиная со следующего рендера.
Обновление состояния на основе предыдущего состояния
Предположим, что age равно 42. Этот обработчик вызывает setAge(age + 1) три раза:
function handleClick() {
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
setAge(age + 1); // setAge(42 + 1)
} Однако после одного клика, age будет только 43, а не 45! Это потому, что вызов функции set не обновляет переменную состояния age в уже работающем коде. Поэтому каждый вызов setAge(age + 1) становится setAge(43).
Чтобы решить эту проблему, можно передать функцию обновления функции setAge вместо следующего состояния:
function handleClick() {
setAge(a => a + 1); // setAge(42 => 43)
setAge(a => a + 1); // setAge(43 => 44)
setAge(a => a + 1); // setAge(44 => 45)
} Здесь, 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 в начале обработчика события.
Однако, если вы производите несколько обновлений в рамках одного события, обновляторы могут быть полезны. Они также полезны, если доступ к переменной состояния неудобен (вы можете столкнуться с этим при оптимизации повторных рендерингов).
Если вы предпочитаете согласованность немного более громоздкому синтаксису, разумно всегда писать обновлятор, если состояние, которое вы устанавливаете, вычисляется из предыдущего состояния. Если оно вычисляется из предыдущего состояния какой-либо другой переменной состояния, вы можете объединить их в один объект и использовать редьюсер.
Обновление объектов и массивов в состоянии
Вы можете поместить объекты и массивы в состояние. В React состояние считается только для чтения, поэтому вы должны заменять его, а не изменять существующие объекты. Например, если у вас есть объект form в состоянии, не изменяйте его:
// 🚩 Don't mutate an object in state like this:
form.firstName = 'Taylor'; Вместо этого замените весь объект, создав новый:
// ✅ Replace state with a new object
setForm({
...form,
firstName: 'Taylor'
}); Прочитайте обновление объектов в состоянии и обновление массивов в состоянии, чтобы узнать больше.
Избегание повторного создания начального состояния
React сохраняет начальное состояние один раз и игнорирует его при последующих рендерах.
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos());
// ... Хотя результат createInitialTodos() используется только для начального рендеринга, вы все равно вызываете эту функцию при каждом рендеринге. Это может быть неэффективно, если она создает большие массивы или выполняет дорогостоящие вычисления.
Для решения этой проблемы вы можете передать её в качестве инициализатора функции в useState:
function TodoList() {
const [todos, setTodos] = useState(createInitialTodos);
// ... Обратите внимание, что вы передаёте createInitialTodos, которая является самой функцией, а не createInitialTodos(), которая является результатом её вызова. Если вы передадите функцию в useState, React вызовет её только во время инициализации.
React может вызвать ваши инициализаторы дважды в режиме разработки, чтобы убедиться, что они чистые.
Сброс состояния с помощью ключа
Вы часто будете сталкиваться с атрибутом key при рендеринге списков. Однако он также выполняет другую функцию.
Вы можете сбросить состояние компонента, передав другой key в компонент. В этом примере кнопка Reset изменяет переменную состояния version, которую мы передаём как key в Form. Когда key изменяется, React повторно создаёт компонент Form (и все его дочерние элементы) с нуля, поэтому его состояние сбрасывается.
Прочитайте сохранение и сброс состояния, чтобы узнать больше.
import { useState } from 'react'; export default function App() { const [version, setVersion] = useState(0); function handleReset() { setVersion(version + 1); } return ( <> <button onClick={handleReset}>Reset</button> <Form key={version} /> </> ); } function Form() { const [name, setName] = useState('Taylor'); return ( <> <input value={name} onChange={e => setName(e.target.value)} /> <p>Hello, {name}.</p> </> ); }
Хранение информации из предыдущих рендерингов
Обычно вы обновляете состояние в обработчиках событий. Однако в редких случаях вы можете настроить состояние в ответ на рендеринг — например, вы можете изменить переменную состояния при изменении свойства.
В большинстве случаев вам это не нужно:
-
Если значение, которое вам нужно, можно вычислить полностью из текущих свойств или другого состояния, удалите это избыточное состояние. Если вы обеспокоены слишком частым перевычислением,
useMemoХук может помочь. - Если вы хотите сбросить состояние всего дерева компонентов, передайте другой
keyв ваш компонент. - Если возможно, обновите все соответствующие состояния в обработчиках событий.
В редких случаях, когда ни одно из этих условий не выполняется, есть шаблон, который вы можете использовать для обновления состояния на основе значений, которые были отображены до сих пор, вызвав функцию set во время рендеринга вашего компонента.
Вот пример. Этот CountLabel компонент отображает свойство count передаваемое ему:
export default function CountLabel({ count }) {
return <h1>{count}</h1>
} Предположим, вы хотите показать, увеличился или уменьшился ли счётчик с момента последнего изменения. Свойство count не сообщает вам об этом — вам нужно отслеживать его предыдущее значение. Добавьте переменную состояния prevCount для отслеживания этого. Добавьте другую переменную состояния, называемую trend, для хранения того, увеличился или уменьшился ли счёт. Сравните prevCount с count, и если они не равны, обновите prevCount и trend. Теперь вы можете показать как текущее значение свойства счётчика, так и то, как оно изменилось с момента последнего рендеринга.
import { useState } from 'react'; export default function CountLabel({ count }) { const [prevCount, setPrevCount] = useState(count); const [trend, setTrend] = useState(null); if (prevCount !== count) { setPrevCount(count); setTrend(count > prevCount ? 'increasing' : 'decreasing'); } return ( <> <h1>{count}</h1> {trend && <p>The count is {trend}</p>} </> ); }
Обратите внимание, что если вы вызываете функцию set во время рендеринга, она должна быть внутри условия, такого как prevCount !== count, и должно быть вызов, такой как setPrevCount(count) внутри условия. В противном случае ваш компонент будет повторно рендериться в цикле до тех пор, пока не упадет. Кроме того, вы можете обновить состояние только текущего рендеринга компонента таким образом. Вызов функции set другого компонента во время рендеринга является ошибкой. Наконец, ваш вызов set все равно должен обновлять состояние без мутации — это не означает, что вы можете нарушить другие правила чистых функций.
Этот шаблон может быть трудно понять, и его обычно лучше избегать. Однако он лучше, чем обновление состояния в эффекте. Когда вы вызываете функцию set во время рендеринга, React повторно рендерит этот компонент сразу после того, как ваш компонент завершится с оператором return, и перед рендерингом дочерних элементов. Таким образом, детям не нужно рендериться дважды. Остальная часть вашей функции компонента по-прежнему будет выполняться (и результат будет отброшен). Если ваше условие находится ниже всех вызовов Хуков, вы можете добавить ранний return; для более раннего перезапуска рендеринга.
Отладка
Я обновил состояние, но лог выводит старое значение
Вызов функции set не изменяет состояние в выполняемом коде:
function handleClick() {
console.log(count); // 0
setCount(count + 1); // Request a re-render with 1
console.log(count); // Still 0!
setTimeout(() => {
console.log(count); // Also 0!
}, 5000);
} Это связано с тем, что состояния ведут себя как моментальные снимки. Обновление состояния запрашивает ещё один рендеринг с новым значением состояния, но не влияет на переменную JavaScript count в уже выполняемом обработчике события.
Если вам нужно использовать следующее состояние, вы можете сохранить его в переменной перед передачей в функцию set:
const nextCount = count + 1;
setCount(nextCount);
console.log(count); // 0
console.log(nextCount); // 1 Я обновил состояние, но экран не обновляется
React проигнорирует ваше обновление, если следующее состояние равно предыдущему состоянию, как определяется сравнением Object.is. Это обычно происходит, когда вы изменяете объект или массив в состоянии напрямую:
obj.x = 10; // 🚩 Wrong: mutating existing object
setObj(obj); // 🚩 Doesn't do anything Вы изменили существующий объект obj и передали его обратно в setObj, поэтому React проигнорировал обновление. Чтобы исправить это, вам нужно убедиться, что вы всегда заменяете объекты и массивы в состоянии, а не изменяете их:
// ✅ Correct: creating a new object
setObj({
...obj,
x: 10
}); Ошибка: «Слишком много повторных рендерингов»
Вы можете получить ошибку, которая говорит: Too many re-renders. React limits the number of renders to prevent an infinite loop. Как правило, это означает, что вы безусловно устанавливаете состояние во время рендеринга, поэтому ваш компонент попадает в цикл: рендеринг, установить состояние (что вызывает рендеринг), рендеринг, установить состояние (что вызывает рендеринг) и так далее. Очень часто это вызвано ошибкой в определении обработчика события:
// 🚩 Wrong: calls the handler during render
return <button onClick={handleClick()}>Click me</button>
// ✅ Correct: passes down the event handler
return <button onClick={handleClick}>Click me</button>
// ✅ Correct: passes down an inline function
return <button onClick={(e) => handleClick(e)}>Click me</button> Если вы не можете найти причину этой ошибки, щелкните стрелку рядом с ошибкой в консоли и просмотрите стек JavaScript, чтобы найти конкретный вызов функции set , ответственный за ошибку.
Моя функция инициализатора или обновлятора запускается дважды
В режиме Strict Mode React вызовет некоторые ваши функции дважды вместо одного:
function TodoList() {
// This component function will run twice for every render.
const [todos, setTodos] = useState(() => {
// This initializer function will run twice during initialization.
return createTodos();
});
function handleClick() {
setTodos(prevTodos => {
// This updater function will run twice for every click.
return [...prevTodos, createTodo()];
});
}
// ... Это ожидаемо и не должно сломать ваш код.
Это только для разработки поведение помогает вам сохранять компоненты чистыми. React использует результат одного из вызовов и игнорирует результат другого вызова. Пока ваши компонент, инициализатор и обновляющие функции чистые, это не должно повлиять на вашу логику. Однако, если они случайно нечистые, это помогает вам заметить ошибки.
Например, эта нечистая функция обновлятора изменяет массив в состоянии:
setTodos(prevTodos => {
// 🚩 Mistake: mutating state
prevTodos.push(createTodo());
}); Поскольку React вызывает вашу функцию обновлятора дважды, вы увидите, что задача была добавлена дважды, поэтому вы поймёте, что есть ошибка. В этом примере вы можете исправить ошибку, заменив массив вместо изменения его:
setTodos(prevTodos => {
// ✅ Correct: replacing with new state
return [...prevTodos, createTodo()];
}); Теперь, когда эта функция обновлятора чистая, вызов её ещё раз не меняет поведение. Вот почему React вызывает её дважды, чтобы помочь вам найти ошибки. Только функции компонента, инициализатора и обновлятора должны быть чистыми. Обработчики событий не должны быть чистыми, поэтому React никогда не будет вызывать ваши обработчики событий дважды.
Прочитайте поддержание чистоты компонентов, чтобы узнать больше.
Я пытаюсь установить состояние в функцию, но она вызывается вместо этого
Вы не можете поместить функцию в состояние таким образом:
const [fn, setFn] = useState(someFunction);
function handleClick() {
setFn(someOtherFunction);
} Поскольку вы передаёте функцию, React предполагает, что someFunction является функцией инициализации, а someOtherFunction — функцией обновления, поэтому он пытается вызвать их и сохранить результат. Чтобы фактически сохранить функцию, необходимо поместить () => перед ними в обоих случаях. Тогда React сохранит переданные вами функции.
const [fn, setFn] = useState(() => someFunction);
function handleClick() {
setFn(() => someOtherFunction);
}
© 2013–present Facebook Inc.
Licensed under the Creative Commons Attribution 4.0 International Public License.
https://18.react.dev/reference/react/useState