«Если рабочий хочет хорошо выполнять свою работу, он должен сначала заточить свои инструменты» — Конфуций, «Аналитики Конфуция. Лу Лингун»
титульная страница > программирование > Реакция: устаревшее закрытие

Реакция: устаревшее закрытие

Опубликовано 2 сентября 2024 г.
Просматривать:387

В этом посте я покажу, как создать замыкание в приложении React с хуком useState.

Я не буду объяснять, что такое замыкание, потому что по этой теме есть много ресурсов, и я не хочу повторяться. Советую прочитать эту статью @imranabdulmalik.

Короче говоря, замыкание это (от Mozilla):

... комбинация функции, объединенной (заключенной) со ссылками на ее окружающее состояние (лексическое окружение). Другими словами, замыкание дает вам доступ к области видимости внешней функции из внутренней функции. В JavaScript замыкания создаются каждый раз при создании функции, во время создания функции.

На случай, если вы не знакомы с термином лексическая среда, вы можете прочитать эту статью @soumyadey или, альтернативно, эту.

Проблема

В приложении React вы можете случайно создать закрытие переменной, принадлежащей состоянию компонента, созданному с помощью ловушки useState. Когда это происходит, вы сталкиваетесь с проблемой устаревшего закрытия, то есть, когда вы ссылаетесь на старое значение состояния, которое за это время изменилось, и поэтому оно не более актуально.

POC

Я создал демонстрационное приложение React, основная цель которого – увеличить счетчик (принадлежащий состоянию), который можно закрыть замыканием в обратном вызове метода setTimeout.

Короче говоря, это приложение может:

  • Показать значение счетчика
  • Увеличить счетчик на 1
  • Запустите таймер, чтобы увеличить счетчик на 1 через пять секунд.
  • Увеличить счетчик на 10

На следующем рисунке показано исходное состояние пользовательского интерфейса приложения со счетчиком, равным нулю.

React: stale closure

Мы смоделируем закрытие счетчика в три этапа:

  1. Увеличение счетчика на 1

React: stale closure

  1. Запуск таймера для увеличения на 1 через пять секунд

React: stale closure

  • Увеличение на 10 до истечения тайм-аута React: stale closure

Через 5 секунд значение счетчика равно 2.

React: stale closure

Ожидаемое значение счетчика должно быть 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 (

    Closure demo


    Counter value: {counter}


    Follow the istructions to create a closure of the state variable counter

    1. Set the counter to preferred value
    2. Start a timeout and wait for {timeOutInSeconds} to increment the counter (current value is {counter})
    3. Increment by 10 the counter before the timeout

    { renderLogs() }
    ); } export default App;

    Решение

    Решение основано на использовании перехватчика 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)
    

    Ресурсы

    • Дмитрий Павлутин Будьте осторожны с устаревшими замыканиями при использовании React Hooks
    • Имран Абдулмалик: Освоение замыканий в JavaScript: подробное руководство
    • Кейур Паралкар Лексическая область действия в JavaScript – Руководство для начинающих
    • Сувик Пол Устаревшие замыкания в React
    • Сумья Дей: Понимание лексической области видимости и замыканий в JavaScript
    • Субаш Махапатра stackoverflow
    Заявление о выпуске Эта статья воспроизведена по адресу: https://dev.to/animusna/react-stale-closure-81a?1. Если есть какие-либо нарушения, свяжитесь с [email protected], чтобы удалить их.
    Последний учебник Более>

    Изучайте китайский

    Отказ от ответственности: Все предоставленные ресурсы частично взяты из Интернета. В случае нарушения ваших авторских прав или других прав и интересов, пожалуйста, объясните подробные причины и предоставьте доказательства авторских прав или прав и интересов, а затем отправьте их по электронной почте: [email protected]. Мы сделаем это за вас как можно скорее.

    Copyright© 2022 湘ICP备2022001581号-3