React.StrictMode: функция SetState в useEffect запускается несколько раз, когда эффект запускается один раз - PullRequest
0 голосов
/ 19 февраля 2020

Вывод приведенного ниже кода, когда oldRunIn равен undefined, соответствует ожидаемому при срабатывании эффекта:

Эффект работает

setState выполняется

Тем не менее, в следующий раз, когда useEffect запускается с определенной переменной состояния runInArrow, которая в функции setState называется oldRunInArrow, вывод:

Эффект выполняется

setState запущен

setState запущен

setState запущен

Как возможно, что эффект запускается только один раз, но setState запускается 3 раз?

const [runInArrow, setRunInArrow] = useState<mapkit.PolylineOverlay[] | undefined>(undefined);


useEffect(() => {
      const trueRunIn = settings.magneticRunIn + magVar;

      const boundingRegion = region.toBoundingRegion();
      const style = new mapkit.Style({
        lineWidth: 5,
        lineJoin: 'round',
        strokeColor: settings.runInColor,
      });
      const newRunInArrow = createArrow(boundingRegion, trueRunIn, style);
      console.log('Effect is running');

      setRunInArrow(oldRunIn => {

        // This runs too many times when oldRunIn aka runInArrow is defined

        console.log('setState is running');
        if (oldRunIn) map.removeOverlays(oldRunIn);
        return newRunInArrow;
      });
      map.addOverlays(newRunInArrow);
  }, [magVar, map, mapkit, region, settings.magneticRunIn, settings.runInColor, settings.showRunIn]);

Чтобы понять, почему я использую функцию для установки состояния, посмотрите это post


Edit:

This странно. если я удаляю if (oldRunIn) map.removeOverlays(oldRunIn);, он работает как положено. Однако, если я изменю его на if (oldRunIn) console.log('oldRunIn is defined');, он все равно будет выполняться несколько раз. Я полностью сбит с толку.

if (oldRunIn) map.addAnnotations([]); не запускается несколько раз.

if (oldRunIn) console.log('test'); выполняется несколько раз.

Это выполняется несколько раз (6 раз за использование всегда):

  setRunInArrow(oldRunIn => {
    console.log('setState is running');
    console.log('test');
    return newRunInArrow;
  });

Это не выполняется несколько раз:

  setRunInArrow(oldRunIn => {
    console.log('setState is running');
    return newRunInArrow;
  });

Редактировать 2:

Воспроизводимый пример. Нажмите на кнопку. Вам даже не нужно иметь if (oldState) console.log('Old state');, который вы можете просто поместить во 2-й console.log, и это изменит поведение.

import { FunctionComponent, useState, useEffect } from 'react';

export const Test: FunctionComponent<> = function Test() {
  const [state, setState] = useState<number | undefined>(undefined);
  const [triggerEffect, setTriggerEffect] = useState(0);

  useEffect(() => {
    console.log('effect is running');
    setState(oldState => {
      console.log('setState is runnning');
      if (oldState) console.log('Old state');
      return 1;
    });
  }, [triggerEffect]);

  return <>
    <button onClick={() => setTriggerEffect(triggerEffect + 1)} type="button">Trigger Effect</button>
  </>;
};

Редактировать 3:

I ' мы воспроизвели это, поместив этот код в другой следующий JS проект. Я предполагаю, что это связано со следующими JS.


Edit 4:

Это не Next JS. Это оборачивает это в React.StrictMode. Вот песочница . Почему?


Редактировать 5:

Как указано в ответе, проблема связана с тем, что StrictMode намеренно запускает код дважды. Он не должен запускать useReducer дважды для документов (это проблема с «ответными крючками / исчерпывающим падением» из моего другого вопроса). Вот демонстрация UseReducer, попробуйте с и без StrictMode. Это также работает дважды. Похоже, он тоже должен быть чистым:

CodeSandbox

import { useState, useEffect, useReducer } from 'react';

function reducer(state, data) {
  console.log('Reducer is running');
  if (state) console.log(state);
  return state + 1;
}

export const Test = function Test() {
  const [state, dispatch] = useReducer(reducer, 1);
  const [triggerEffect, setTriggerEffect] = useState(0);

  useEffect(() => {
    dispatch({});
  }, [triggerEffect]);

  return (
    <>
      <button onClick={() => setTriggerEffect(triggerEffect + 1)} type="button">
        Trigger Effect
      </button>
    </>
  );
};

const Home = () => (
  <React.StrictMode>
    <Test></Test>
  </React.StrictMode>
);

export default Home;

1 Ответ

2 голосов
/ 19 февраля 2020

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

Как отмечено в этой статье :

Он выполняет код ДВАЖДЫ

Еще одна вещь, которую делает React Strict Mode, - это запуск определенных обратных вызовов / методов дважды (ТОЛЬКО в режиме DEV). Вы правильно прочитали! Следующие обратные вызовы / методы будут выполняться дважды в строгом режиме (ТОЛЬКО в режиме DEV):

  • Метод конструктора компонента класса
  • Метод рендеринга (включает компоненты функции)
  • функции обновления setState (первый аргумент)
  • жизненный цикл stati c getDerivedStateFromProps
  • функция обратного вызова инициализатора состояния React.useState
  • обратный вызов React.useMemo

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

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

Обратите внимание, что обратные вызовы useEffect и useLayoutEffect не вызываются дважды даже в режиме dev + строгом режиме, поскольку весь смысл этих обратных вызовов заключается в выполнении побочных эффектов.

Обратите внимание, что я также заметил, что редуктор, который вы передаете React.useReducer, не вызывается дважды в режиме разработки. Я не уверен, почему это так, потому что я чувствую, что это может также принести пользу от такого рода предупреждений.

Как видно выше, это поведение включено, чтобы помочь вам находить ошибки в вашем коде , Поскольку функции средства обновления должны быть идемпотентными и чистыми, запуск их дважды, а не один раз, не должен влиять на функциональность вашего приложения. Если это так, то это ошибка в вашем коде.

Похоже, что метод map.removeOverlays(), который вы вызываете, является эффективной функцией, поэтому его не следует вызывать в функции обновления состояния. Я могу понять, почему вы реализовали это таким образом, основываясь на ответах на другой ваш вопрос , но я думаю, что использование ответа chitova263 решит эту проблему.

...