В этом посте я покажу, как создать замыкание в приложении React с хуком useState.
Я не буду объяснять, что такое замыкание, потому что по этой теме есть много ресурсов, и я не хочу повторяться. Советую прочитать эту статью @imranabdulmalik.
Короче говоря, замыкание это (от Mozilla):
... комбинация функции, объединенной (заключенной) со ссылками на ее окружающее состояние (лексическое окружение). Другими словами, замыкание дает вам доступ к области видимости внешней функции из внутренней функции. В JavaScript замыкания создаются каждый раз при создании функции, во время создания функции.
На случай, если вы не знакомы с термином лексическая среда, вы можете прочитать эту статью @soumyadey или, альтернативно, эту.
В приложении React вы можете случайно создать закрытие переменной, принадлежащей состоянию компонента, созданному с помощью ловушки useState. Когда это происходит, вы сталкиваетесь с проблемой устаревшего закрытия, то есть, когда вы ссылаетесь на старое значение состояния, которое за это время изменилось, и поэтому оно не более актуально.
Я создал демонстрационное приложение React, основная цель которого – увеличить счетчик (принадлежащий состоянию), который можно закрыть замыканием в обратном вызове метода setTimeout.
Короче говоря, это приложение может:
На следующем рисунке показано исходное состояние пользовательского интерфейса приложения со счетчиком, равным нулю.
Мы смоделируем закрытие счетчика в три этапа:
Через 5 секунд значение счетчика равно 2.
Ожидаемое значение счетчика должно быть 12, но мы получаем 2.
Причина, по которой это происходит, заключается в том, что мы создали закрытие счетчика в обратном вызове, передаваемом в setTimeout, и когда срабатывает таймаут, мы устанавливаем счетчик, начиная с его старое значение (это было 1).
setTimeout(() => { setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`]) setTimeoutInProgress(false) setStartTimeout(false) setCounter(counter 1) setLogs((l) => [...l, `Did you create a closure of counter?`]) }, timeOutInSeconds * 1000);
Полный код компонента приложения.
function App() { const [counter, setCounter] = useState(0) const timeOutInSeconds: number = 5 const [startTimeout, setStartTimeout] = useState (false) const [timeoutInProgress, setTimeoutInProgress] = useState (false) const [logs, setLogs] = useState >([]) useEffect(() => { if (startTimeout && !timeoutInProgress) { setTimeoutInProgress(true) setLogs((l) => [...l, `Timeout scheduled in ${timeOutInSeconds} seconds`]) setTimeout(() => { setLogs((l) => [...l, `You closed counter with value: ${counter}\n and now I'll increment by one. Check the state`]) setTimeoutInProgress(false) setStartTimeout(false) setCounter(counter 1) setLogs((l) => [...l, `Did you create a closure of counter?`]) }, timeOutInSeconds * 1000); } }, [counter, startTimeout, timeoutInProgress]) function renderLogs(): React.ReactNode { const listItems = logs.map((log, index) => {log} ); return{listItems}
; } function updateCounter(value: number) { setCounter(value) setLogs([...logs, `The value of counter is now ${value}`]) } function reset() { setCounter(0) setLogs(["reset done!"]) } return (); } export default App;Closure demo
Counter value: {counter}
Follow the istructions to create a closure of the state variable counter
- Set the counter to preferred value
- Start a timeout and wait for {timeOutInSeconds} to increment the counter (current value is {counter})
- Increment by 10 the counter before the timeout
{ renderLogs() }
Решение основано на использовании перехватчика useRef, который позволяет ссылаться на значение, которое не требуется для рендеринга.
Итак, мы добавляем в компонент App:
const currentCounter = useRef(counter)
Затем мы изменим обратный вызов setTimeout, как показано ниже:
setTimeout(() => { setLogs((l) => [...l, `You closed counter with value: ${currentCounter.current}\n and now I'll increment by one. Check the state`]) setTimeoutInProgress(false) setStartTimeout(false) setCounter(currentCounter.current 1) setLogs((l) => [...l, `Did you create a closure of counter?`]) }, timeOutInSeconds * 1000);
Наш обратный вызов должен прочитать значение счетчика, потому что мы регистрируем текущее значение, прежде чем увеличивать его.
В случае, если вам не нужно читать значение, вы можете избежать закрытия счетчика, просто используя функциональную нотацию для обновления счетчика.
seCounter(c => c 1)
Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.
Copyright© 2022 湘ICP备2022001581号-3