React.StrictMode:useEffect中的SetState函数在effect运行一次时运行多次

Die*_*sel 3 javascript reactjs next.js react-hooks

undefined当oldRunIn触发效果时,以下代码的输出符合预期:

效果正在运行

setState 正在运行

但是,下次 useEffect 运行时runInArrow定义了状态变量(在 setState 函数中引用),oldRunInArrow输出为:

效果正在运行

setState 正在运行

setState 正在运行

setState 正在运行

怎么可能effect只运行了一次而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]);
Run Code Online (Sandbox Code Playgroud)

要了解为什么我使用函数来设置状态,请参阅这篇文章


编辑:

这很奇怪。如果我删除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;
  });
Run Code Online (Sandbox Code Playgroud)

这不会运行多次:

  setRunInArrow(oldRunIn => {
    console.log('setState is running');
    return newRunInArrow;
  });
Run Code Online (Sandbox Code Playgroud)

编辑2:

可重现的例子。单击按钮。您甚至不需要只需if (oldState) console.log('Old state');放入第二个 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>
  </>;
};
Run Code Online (Sandbox Code Playgroud)

编辑3:

我通过将此代码放入另一个 nextJS 项目中来重现此内容。我猜这与 nextJS 有关。


编辑4:

这不是 NextJS。它将它包装在 React.StrictMode 中。这里是一个沙箱。为什么?


编辑5:

正如答案指出的那样,问题是由于 StrictMode 故意运行代码两次造成的。它不应该根据文档运行 useReducer 两次(这是我的其他问题中的“react-hooks/exhaustive-deps”的问题)。这是一个 UseReducer 演示,尝试使用和不使用 StrictMode。它还运行两次。似乎它也需要纯净:

代码沙盒

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;
Run Code Online (Sandbox Code Playgroud)

JLR*_*she 9

正如您所发现的,当您使用 React 严格模式时就会发生这种情况,并且这是故意的。

正如本文所述:

它运行代码两次

React 严格模式所做的另一件事是运行某些回调/方法两次(仅在 DEV 模式下)。你没看错!以下回调/方法将在严格模式下运行两次(仅在开发模式下):

  • 类组件构造方法
  • render方法(包括函数组件)
  • setState 更新器函数(第一个参数)
  • 静态 getDerivedStateFromProps 生命周期
  • React.useState 状态初始值设定项回调函数
  • React.useMemo 回调

查看此codesandbox,它在钩子回调和类方法中记录到控制台,以向您显示某些事情发生了两次。

React 这样做是因为它无法可靠地警告您在这些方法中产生的副作用。但如果这些方法是幂等的,那么多次调用它们不会造成任何麻烦。如果它们不是幂等的,那么您应该注意到一些有趣的事情,您应该能够注意到并修复这些事情。

请注意,即使在开发模式+严格模式下,useEffect 和 useLayoutEffect 回调也不会被调用两次,因为这些回调的全部目的是执行副作用。

请注意,我还观察到传递给 React.useReducer 的减速器在开发模式下不会被调用两次。我不确定为什么会这样,因为我觉得这也可以从这种警告中受益。

如上所示,包含此行为是为了帮助您查找代码中的错误。由于更新程序函数应该是幂等且纯粹的,因此运行它们两次而不是一次应该不会对应用程序的功能产生影响。如果确实如此,那么这就是您的代码中的错误。

听起来map.removeOverlays()您正在调用的方法是一个有效的函数,因此不应在状态更新器函数中调用它。我可以根据您的其他问题的答案明白为什么您以这种方式实现它,但我认为使用 chitova263 的答案可以解决这个问题。