Пример использования крюка useEffect: что вызывает повторную визуализацию? - PullRequest
7 голосов
/ 14 июня 2019

Я пытаюсь выяснить, когда useEffect вызывает повторное рендеринг. Я очень удивлен результатом следующего примера:

https://codesandbox.io/embed/romantic-sun-j5i4m

function useCounter(arr = [1, 2, 3]) {
  const [counter, setCount] = useState(0);
  useEffect(() => {
    for (const i of arr) {
      setCount(i);
      console.log(counter);
    }
  }, [arr]);
}

function App() {
  useCounter();
  console.log("render");
  return <div className="App" />;
}

Результат этого примера следующий:

enter image description here

Моя путаница проистекает из двух вещей: я не знаю почему:

  1. Компонент рендерится только три раза (я бы предположил, что компонент рендерится при каждом вызове setCount + один начальный рендеринг - так 4 раза)
  2. Счетчик всегда имеет только два значения 0 и 3: я полагаю, что, как говорится в статье , каждый рендер видит свое собственное состояние и поддерживает, поэтому весь цикл будет выполняться с каждым состоянием в качестве константы ( 1, 2, 3) -> Но почему государство никогда не бывает 2?

Я был бы очень рад, если бы кто-то мог прояснить. Спасибо!

Ответы [ 3 ]

3 голосов
/ 14 июня 2019

Я сделаю все возможное, чтобы объяснить (или пройтись), что происходит. Я также делаю два предположения, в пункте 7 и пункте 10.

  1. Компоненты приложения монтируются.
  2. useEffect вызывается после монтажа.
  3. useEffect будет «сохранять» исходное состояние, поэтому counter будет 0 при обращении к нему внутри.
  4. Цикл запускается 3 раза. Каждая итерация setCount вызывается для обновления счетчика, и журнал консоли регистрирует счетчик, который в соответствии с «сохраненной» версией равен 0. Таким образом, число 0 записывается в консоли 3 раза. Поскольку состояние изменилось (0 -> 1, 1 -> 2, 2 -> 3), наборы React, такие как флаг или что-то, о чем нужно помнить, чтобы они перерисовывались.
  5. React ничего не рендерил во время выполнения useEffect и вместо этого ждет, пока не будет выполнено useEffect для рендеринга.
  6. Как только useEffect будет выполнен, React помнит, что состояние counter изменилось во время его выполнения, таким образом, он будет повторно визуализировать приложение.
  7. Приложение перерисовывается и useCounter вызывается снова. Обратите внимание, что параметры не передаются в пользовательский хук useCounter. Предположение: Я тоже не знал этого, но я думаю, что параметр по умолчанию, кажется, создается снова или, по крайней мере, таким образом, что React думает, что он новый. И, таким образом, поскольку arr видится новым, крючок useEffect будет запущен снова. Это единственная причина, по которой я могу объяснить, что useEffect работает во второй раз.
  8. Во время второго запуска useEffect значение counter будет равно 3. Таким образом, журнал консоли будет записывать число 3 три раза, как и ожидалось.
  9. После повторного запуска useEffect React обнаружил, что во время выполнения счетчик изменился (3 -> 1, 1 -> 2, 2 -> 3) и, таким образом, приложение выполнит повторный рендеринг, вызвав третий ' render 'log.
  10. Предположение: , поскольку внутреннее состояние хука useCounter не изменилось между этим рендером и предыдущим с точки зрения приложения, он не выполняет код внутри него и таким образом, useEffect не называется третий раз. Таким образом, при первом рендеринге приложения он всегда запускает код ловушки. Второе приложение увидело, что внутреннее состояние хука изменило значение counter с 0 на 3 и, таким образом, решает перезапустить его, и в третий раз приложение видит, что внутреннее состояние было 3 и все еще равно 3, поэтому оно решает не повторять это. Это лучшая причина, по которой я могу придумать, чтобы крючок больше не запускался. Вы можете поместить бревно в сам крючок, чтобы убедиться, что оно не влияет на запуск в третий раз.

Это то, что я вижу, я надеюсь, это немного прояснилось.

3 голосов
/ 15 июня 2019

Я нашел объяснение для третьего рендера в реактивной документации. Я думаю, что это проясняет, почему реагирует третий рендер без применения эффекта:

Если вы обновите State Hook до того же значения, что и текущее состояние, Реакция выручит без рендеринга детей или эффектов стрельбы. (React использует алгоритм сравнения Object.is.)

Обратите внимание, что React, возможно, все еще нужно будет визуализировать этот конкретный компонент снова перед спасением. Это не должно быть проблемой, потому что React не будет излишне уходите «глубже» в дерево. Если вы делаете дорого Расчеты во время рендеринга, вы можете оптимизировать их с помощью useMemo.

Кажется, что useState и useReducer разделяют эту логику спасения.

1 голос
/ 14 июня 2019

setState и аналогичные перехваты не сразу перерисовывают ваш компонент.Они могут пакетировать или отложить обновление до позже.Таким образом, вы получаете только один повтор после последней setCount с counter === 3.

Вы получаете начальный рендер с counter === 0 и два дополнительных рендера с counter === 3.Я не уверен, почему это не идет к бесконечному циклу.arr = [1, 2, 3] должен создавать новый массив при каждом вызове и запускать useEffect:

  1. Первоначальный рендеринг устанавливает counter в 0
  2. useEffect logs 0 threeраз устанавливает counter в 3 и запускает повторное рендеринг
  3. первый рендеринг с counter === 3
  4. useEffect logs 3 три раза, устанавливает counter в 3и ???

Реакция должна либо остановиться здесь, либо перейти к бесконечному циклу с шага 3.

...