useRef для хранения значения предыдущего состояния

Меня смущает приведенное ниже использование useRef для хранения предыдущего значения состояния. По сути, как правильно отобразить предыдущее значение. Поскольку useEffect зависит от значения, я понял, что каждый раз, когда значение изменяется (т.е. когда пользователь обновляет текстовое поле), он будет обновлять prevValue.current до нового введенного значения.

Но, похоже, это не то, что происходит. Какая последовательность действий в этом случае?

function App() {
  const [value, setValue] =  useState("");
  const prevValue = useRef('')
  useEffect(() => {
    prevValue.current = value;
  }, [value]);
  return (
    <div>
      <input
        value={value}
        onChange={e => setValue(e.target.value)}
      />
      <div>
        Curr Value: {value}
      </div>
      <div>
        Prev Value: {prevValue.current}
      </div>
    </div>
  );
}

person copenndthagen    schedule 19.01.2021    source источник


Ответы (3)


Хорошо, хотя технически это работает, это сбивает с толку и может привести к ошибкам при добавлении дополнительных материалов. Причина, по которой это работает, заключается в том, что useEffect запускается после изменений состояния, а изменение значений ref не вызывает повторного рендеринга. Лучшим способом было бы обновить значение ref в обработчике onChange, а не в эффекте. Но код, который вы разместили, работает следующим образом:

  1. Изначально оба будут пустыми
  2. Пользователь что-то вводит, вызывая изменение состояния через setValue
  3. Это вызывает повторную визуализацию, поэтому {value} является новым значением, но, поскольку ссылка еще не была обновлена, {prevValue.current} по-прежнему отображается как старое значение.
  4. Затем, после рендеринга, эффект запускается, поскольку он имеет value в качестве зависимости. Таким образом, этот эффект обновляет ссылку, чтобы содержать значение состояния CURRENT.
  5. Однако, поскольку изменение значения ref не вызывает повторного рендеринга, новое значение не отражается в том, что рендерится.

Итак, как только эти шаги завершатся, тогда да, технически значение состояния и ref - это одно и то же значение. Однако, поскольку изменение ссылки не привело к повторному рендерингу, оно по-прежнему показывает старое значение ссылки в том, что рендерится.

Это, очевидно, не очень хорошо, потому что, если что-то еще запускает повторную визуализацию, например, если у вас есть другой вход со значением подключенного состояния, тогда да, {prevValue.current} затем повторно отобразит как текущий {value}, а затем технически это неправильно, потому что это покажет текущее значение, а не предыдущее значение.

Таким образом, хотя технически он работает в этом случае использования, он будет подвержен ошибкам при добавлении большего количества кода, и затрудняет оборачивание головы вокруг

person Jayce444    schedule 19.01.2021
comment
Спасибо ... Очень близко к тому, что я ищу ... Но просто пояснение к шагу 3 ... Когда вы говорите, что это вызывает повторную визуализацию, когда это происходит, не следует ли запускать код useEffect немедленно, т.е. должен войти внутрь useEffect и prevValue.current должен получить новое значение? - person copenndthagen; 19.01.2021
comment
@testndtv нет, он не запускается во время рендеринга, он запускается после рендеринга и после обновления DOM. Вы можете увидеть это в официальных документах React: reactjs.org/docs/hooks-effect.html - person Jayce444; 19.01.2021
comment
О, к ... Вы имеете в виду это утверждение на той странице .... Что делает useEffect? Используя этот Hook, вы сообщаете React, что ваш компонент должен что-то сделать после рендеринга. React запомнит переданную вами функцию (мы будем называть ее нашим «эффектом») и вызовет ее позже, после выполнения обновлений DOM. - person copenndthagen; 19.01.2021
comment
Итак, по сути, DOM сначала рендерится / обновляется, а после этого запускается useEffect? - person copenndthagen; 19.01.2021
comment
Да, правильно, поэтому useEffect в основном говорит, что после каждого повторного рендеринга, когда DOM обновляется и все готово, если одна из моих зависимостей изменилась, запустите этот код. - person Jayce444; 19.01.2021
comment
Хорошо, круто ... и useState будет немного отличаться в том смысле, что он немедленно обновляется и отражается в DOM, например. Curr Value: в данном случае {value} - person copenndthagen; 19.01.2021
comment
Да, правильно, поэтому внутренне в React при изменении состояния значение состояния изменяется, ТОГДА компонент выполняет повторный рендеринг. Так что это сразу отражается - person Jayce444; 19.01.2021
comment
Я хочу добавить что-то здесь, когда состояние изменяется, это вызывает повторный рендеринг, состояние обновляется и применяется к Dom. После обновления DOM будет запущен useEffect. - person Subrato Patnaik; 19.01.2021
comment
@ Jayce444 - Большое спасибо ... Последний связанный вопрос ... Когда именно запускается функция возврата / очистки внутри useEffect (если она определена)? - person copenndthagen; 19.01.2021
comment
@testndtv после размонтирования компонента в основном воссоздает componentDidUnmount. В частности, я считаю, что это происходит после его удаления из DOM и до того, как объект компонента будет помечен JS-движком для сборки мусора. - person Jayce444; 19.01.2021

https://reactjs.org/docs/hooks-reference.html#useref useRef возвращает изменяемый объект ref, свойство .current которого инициализировано переданным аргументом (initialValue). The returned object will persist for the full lifetime of the component.

https://reactjs.org/docs/hooks-effect.html Используется ли эффект запуска после каждого рендера? Да! По умолчанию запускается both after the first render and after every update

Так происходит в последовательности шагов:

1 - Input change (example: "1")
2 - Component re-render
3 - useEffect run and set value ("1") to prevValue.current. This does not make component re-render. At this time prevValue.current is "1".
4 - Input change (example: "12")
5 - Component re-render => show prevValue.current was set before in step 3 ("1")
6 - useEffect run and set value ("12") to prevValue.current. This does not make component re-render. At this time prevValue.current is "12".
... 
person Thanh    schedule 19.01.2021
comment
Спасибо ... Но для шага (3) при запуске useEffect значение не должно быть равным обновленному / новому значению, и, следовательно, prevValue.current также должен быть обновленным / новым значением? Это мой конкретный вопрос - person copenndthagen; 19.01.2021
comment
Например, на шаге 1 вы вводите 1, затем на шаге 3, когда запускается useEffect, value будет 1, и он будет установлен prevValue.current, поэтому prevValue.current будет иметь значение 1. value in useEffect всегда будет новым / обновленным значением ввода. - person Thanh; 19.01.2021
comment
@testndtv Обратите внимание, что useEffect запускается ПОСЛЕ первого рендеринга и ПОСЛЕ каждого обновления - person Thanh; 19.01.2021

useRef() используется для сохранения значений в последовательных отрисовках. Если вы хотите сохранить прошлое значение, поместите его в onChange:

<input
    value={value}
    onChange={e => {
       prevValue.current = value;
       setValue(e.target.value)
    }}
   />

Это присвоит ему текущее значение состояния value до того, как оно будет изменено, и вам не понадобится ловушка useEffect.

person marsh    schedule 19.01.2021