речь идет о функциональных компонентах, имеющих useState
скажем
const [age, setAge] = useState(0)
теперь скажем при обновлении age
я должен использовать предыдущий age
В документах React упоминается что-то под названием ФУНКЦИОНАЛЬНЫЕ ОБНОВЛЕНИЯ, где вы можете передать функцию, а аргументом для нее будет предыдущее значение состояния, например.
setState((previousAge) => previousAge + 1)
зачем мне это делать, когда я могу просто сделать
setState(previousAge + 1)
в чем преимущества использования функционала setState
,
Я знаю, что в компонентах на основе классов было что-то, называемое группированием обновлений состояния функциональным способом, но я не могу найти ничего подобного в документации по функциональным компонентам.
Просто для ясности, понятие «функциональные обновления» (функциональное программирование) подразумевает определенные вещи. Вы должны знать, что внутри объекты состояния реакции на самом деле хранятся в виде серии запомненных функций. Таким образом, используя функцию в качестве параметра setState, вы просто добавляете еще одну чистую функцию в стек мемоизированных функций.
См., например. stackoverflow.com/questions/53024496/… или stackoverflow.com/questions/56782079/react-hooks-stale-state для примеров
Потому что, если вы этого не сделаете, вы обнаружите, что в какой-то момент вы получите старое значение для age
. Проблема в том, что иногда то, что вы предлагаете, срабатывает. Но иногда этого не будет. Это может не сломать ваш текущий код сегодня, но может сломать другой код, который вы написали несколько недель назад, или ваш текущий код через несколько месяцев.
Симптом действительно странный. Вы можете напечатать значение переменной внутри компонента jsx, используя синтаксис {x}
, а затем распечатать ту же переменную, используя console.info
, после рендеринга компонента jsx (не раньше) и обнаружить, что значение console.info
устарело — console.info
, которое происходит после рендеринга. каким-то образом имеют более старое значение, чем рендеринг.
Таким образом, фактическое значение переменных состояния не всегда может корректно работать в обычном коде — они предназначены только для возврата последнего значения при рендеринге. По этой причине механизм обратного вызова в установщике состояния был реализован, чтобы позволить вам получить последнее значение переменной состояния в обычном коде вне рендеринга.
Проблемы могут возникнуть в зависимости от того, как быстро/часто вызывается ваш сеттер.
Если вы используете простой способ получения значения из замыкания, последующие вызовы между двумя рендерингами могут не дать желаемого эффекта.
Простой пример:
function App() {
const [counter, setCounter] = useState(0);
const incWithClosure = () => {
setCounter(counter + 1);
};
const incWithUpdate = () => {
setCounter(oldCounter => oldCounter + 1);
};
return (<>
<button onClick = {_ => { incWithClosure(); incWithClosure(); }}>
Increment twice using incWithClosure
</button>
<button onClick = {_ => { incWithUpdate(); incWithUpdate(); }}>
Increment twice using incWithUpdate
</button>
<p>{counter}</p>
</>);
}
Обе кнопки дважды вызывают один из методов увеличения. Но мы наблюдаем:
Когда это может произойти?
incWithClosure
вызывается несколько раз сразу после друг другаПример с асинхронной работой (имитация загрузки ресурса):
function App() {
const [counter, setCounter] = useState(0);
const incWithClosureDelayed = () => {
setTimeout(() => {
setCounter(counter + 1);
}, 1000);
};
const incWithUpdateDelayed = () => {
setTimeout(() => {
setCounter((oldCounter) => oldCounter + 1);
}, 1000);
};
return (
<>
<button onClick = {(_) => incWithClosureDelayed()}>
Increment slowly using incWithClosure
</button>
<button onClick = {(_) => incWithUpdateDelayed()}>
Increment slowly using incWithUpdate
</button>
<p>{counter}</p>
</>
);
}
Нажмите на первую кнопку дважды (в течение одной секунды) и обратите внимание, что счетчик увеличивается только на 1. Вторая кнопка имеет правильное поведение.
Они не совпадают, если ваше обновление зависит от предыдущего значения, найденного в состоянии, вам следует использовать функциональную форму. Если вы не используете функциональную форму в этом случае, ваш код когда-нибудь сломается.
Почему ломается и когда
Функциональные компоненты React — это просто замыкания, значение состояния, которое у вас есть в замыкании, может быть устаревшим — это означает, что значение внутри замыкания не соответствует значению, которое находится в состоянии React для этого компонента, это может произойти в следующие случаи:
1- асинхронные операции (В этом примере нажмите медленное добавление, а затем несколько раз нажмите кнопку добавления, позже вы увидите, что состояние было сброшено до того, что было внутри замыкания, когда была нажата кнопка медленного добавления)
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick = {() => {
setCounter(counter + 1);
}}
>
immediately add
</button>
<button
onClick = {() => {
setTimeout(() => setCounter(counter + 1), 1000);
}}
>
Add
</button>
</>
);
};
2- Когда вы вызываете функцию обновления несколько раз в одном закрытии
const App = () => {
const [counter, setCounter] = useState(0);
return (
<>
<p>counter {counter} </p>
<button
onClick = {() => {
setCounter(counter + 1);
setCounter(counter + 1);
}}
>
Add twice
</button>
</>
);
}
спасибо за прикрепление рабочего примера.
Это действительно помогло мне четко понять концепцию. Вы должны сделать это доступным в других связанных вопросах, потому что действительно трудно понять ответы, упомянутые там. Золото.
Использование функции позволяет React внутренне оптимизировать разрешение многих вызовов setState. Для простых приложений в этом нет необходимости. Но для будущего приложения, которое может стать очень большим, может быть хорошим выбором запустить все ваши сеттеры таким образом.