React.StrictMode:useEffect 中的 SetState 函数在 effect 运行一次时运行多次 [英] React.StrictMode: SetState function in useEffect is run multiple times when effect is run once

查看:44
本文介绍了React.StrictMode:useEffect 中的 SetState 函数在 effect 运行一次时运行多次的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

当oldRunIn为undefined时,下面代码的输出与触发效果时的预期一致:

The output of the code below when oldRunIn is undefined is as expected when the effect is triggered:

效果正在运行

setState 正在运行

setState is running

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

However, the next time useEffect runs with the state variable runInArrow defined, which is referred to as oldRunInArrow in the setState function the output is:

效果正在运行

setState 正在运行

setState is running

setState 正在运行

setState is running

setState 正在运行

setState is running

怎么可能效果只运行一次而setState运行了3次?

How is it possible that the effect runs only one time but the setState runs 3 times?

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

To understand why I'm using a function to set state see this post

这很奇怪.如果我删除 if (oldRunIn) map.removeOverlays(oldRunIn); 它会按预期工作.但是,如果我将其更改为 if (oldRunIn) console.log('oldRunIn is defined'); 它仍然会运行多次.我彻底糊涂了.

This is strange. if I remove if (oldRunIn) map.removeOverlays(oldRunIn); it works as expected. However if I change it to if (oldRunIn) console.log('oldRunIn is defined'); it still runs multiple times. I'm thoroughly confused.

if (oldRunIn) map.addAnnotations([]); 不会多次运行.

if (oldRunIn) console.log('test'); 确实运行了多次.

这会运行多次(每个使用效果 6 次)始终:

This runs multiple times (6 times per use effect) always:

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

这不会多次运行:

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


编辑 2:


Edit 2:

可重现的示例.单击按钮.您甚至不需要 if (oldState) console.log('Old state'); 您只需放入第二个 console.log 即可更改行为.

Reproducible example. Click the button. You don't even need to have if (oldState) console.log('Old state'); you can just put in 2nd console.log and it will change the behavior.

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:


Edit 3:

我通过将此代码放入另一个 nextJS 项目来复制此内容.我猜这与 nextJS 有关.

I've reproduced this by putting this code into another nextJS project. I'm guessing it has to do with nextJS.

编辑 4:

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

It's not NextJS. It's wrapping it in React.StrictMode. Here is a sandbox. Why?

编辑 5:

正如答案所指出的,问题是由于 StrictMode 故意运行代码两次.它不应该在每个文档中运行 useReducer 两次(这是我的另一个问题中的react-hooks/exhaustive-deps"的一个问题).这是一个 UseReducer 演示,尝试使用和不使用 StrictMode.它也运行两次.好像也需要纯:

As the answer pointed out the issue is due to StrictMode intentionally running code twice. It should not run useReducer twice per the docs (this is an issue with "react-hooks/exhaustive-deps" from my other question). Here is a UseReducer demo, try with and without StrictMode. It also runs twice. Seems like it needs to be pure too:

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;

推荐答案

如您所见,这是在您使用 React 严格模式时发生的,而且是有意为之.

As you've figured out, this is happening when you use React strict mode, and it is intentional.

本文所述:

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

It runs code TWICE

Another thing that React Strict Mode does is run certain callbacks/methods twice (in DEV mode ONLY). You read that right! The following callbacks/methods will be run twice in Strict Mode (in DEV mode ONLY):

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

Checkout 这个codeandbox,它以钩子回调和类方法登录到控制台,向您展示某些事情发生了两次.

Checkout this codesandbox which logs to the console in hook callbacks and class methods to show you that certain things happen twice.

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

React does this because it cannot reliably warn you against side-effects you do in those methods. But if those methods are idempotent, then calling them multiple times shouldn't cause any trouble. If they are not idempotent, then you should notice funny things which you should hopefully be able to notice and fix.

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

Note that useEffect and useLayoutEffect callbacks are not called twice even in dev mode + strict mode because the entire point of those callbacks is to perform side-effects.

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

Note that I also observed that the reducer you pass to React.useReducer is not called twice in dev mode. I'm not sure why this is because I feel like that could also benefit from this kind of warning.

如上所示,包含此行为是为了帮助您查找代码中的错误.由于更新程序函数应该是幂等的和纯的,运行它们两次而不是一次应该不会影响您的应用程序的功能.如果是这样,则说明您的代码存在错误.

As seen above, this behavior is included to help you find bugs in your code. Since the updater functions should be idempotent and pure, running them twice instead of once should have no effect on your app's functionality. If it does, then that's a bug in your code.

听起来您正在调用的 map.removeOverlays() 方法是一个有效的函数,因此不应在状态更新程序函数中调用它.根据对 你的其他问题,但我认为使用 chitova263 的答案可以解决这个问题.

It sounds like the map.removeOverlays() method you're calling is an effectful function, so it should not be called within a state updater function. I can see why you've implemented it this way based on the answers to your other question, but I think using chitova263's answer would remedy this issue.

这篇关于React.StrictMode:useEffect 中的 SetState 函数在 effect 运行一次时运行多次的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆