React.StrictMode: useEffect 中的 SetState 函数是 运行 多次,而 effect 是 运行 一次
React.StrictMode: SetState function in useEffect is run multiple times when effect is run once
当 oldRunIn 为 undefined
时,下面代码的输出与触发效果时的预期一致:
Effect is running
setState is running
但是,下一次 useEffect 运行s 定义了状态变量 runInArrow
,在 setState 函数中称为 oldRunInArrow
,输出为:
Effect is running
setState is running
setState is running
setState is running
怎么可能effect 运行只有1次,而setState 运行s 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]);
要了解我为什么要使用函数来设置状态,请参阅此
编辑:
这很奇怪。如果我删除 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');
你可以只输入 2nd 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:
我通过将此代码放入另一个 nextJS 项目来重现它。我猜这与 nextJS 有关。
编辑 4:
这不是 NextJS。它将它包装在 React.StrictMode 中。这里有一个sandbox。为什么?
编辑 5:
正如答案指出的那样,问题是由于 StrictMode 故意 运行ning 代码两次。根据文档,它不应该 运行 useReducer 两次(这是我另一个问题中“react-hooks/exhaustive-deps”的问题)。这是一个 UseReducer 演示,尝试使用和不使用 StrictMode。它还 运行s 两次。好像也需要纯的:
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 严格模式时发生的,而且是有意为之。
如 this article 所述:
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):
- Class component constructor method
- The render method (includes function components)
- setState updater functions (the first argument)
- The static getDerivedStateFromProps lifecycle
- The React.useState state initializer callback function
- The React.useMemo callback
Checkout this codesandbox which logs to the console in hook callbacks and class methods to show you that certain things happen twice.
React 这样做是因为它无法可靠地警告您在这些方法中产生的副作用。但如果这些方法是幂等的,那么多次调用它们应该不会造成任何麻烦。如果它们不是幂等的,那么您应该注意到有趣的事情,希望您能够注意到并修复这些事情。
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.
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.
如上所示,包含此行为是为了帮助您查找代码中的错误。由于更新程序函数应该是幂等的和纯粹的,运行 它们两次而不是一次应该不会影响您的应用程序的功能。如果是这样,那就是您的代码中存在错误。
听起来您正在调用的 map.removeOverlays()
方法是一个有效的函数,因此不应在状态更新函数中调用它。根据 的答案,我可以理解为什么您以这种方式实现它,但我认为使用 chitova263 的答案可以解决这个问题。
我是 运行 这段代码,默认情况下 useEffect() 在组件安装时最初被调用一次?
import React, { useState, useEffect } from 'react';
const ChangeDOMTitle = (props) => {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
useEffect(() => {
console.log("Updating the count");
document.title = `Clicked ${count} times`;
});
return (
<React.Fragment>
<input type="text" value={name}
onChange={event => setName(event.target.value)} />
<button onClick={() => setCount(count + 1)}>Clicked {count} times</button>
</React.Fragment>
);
};
export default ChangeDOMTitle;
当 oldRunIn 为 undefined
时,下面代码的输出与触发效果时的预期一致:
Effect is running
setState is running
但是,下一次 useEffect 运行s 定义了状态变量 runInArrow
,在 setState 函数中称为 oldRunInArrow
,输出为:
Effect is running
setState is running
setState is running
setState is running
怎么可能effect 运行只有1次,而setState 运行s 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]);
要了解我为什么要使用函数来设置状态,请参阅此
编辑:
这很奇怪。如果我删除 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');
你可以只输入 2nd 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:
我通过将此代码放入另一个 nextJS 项目来重现它。我猜这与 nextJS 有关。
编辑 4:
这不是 NextJS。它将它包装在 React.StrictMode 中。这里有一个sandbox。为什么?
编辑 5:
正如答案指出的那样,问题是由于 StrictMode 故意 运行ning 代码两次。根据文档,它不应该 运行 useReducer 两次(这是我另一个问题中“react-hooks/exhaustive-deps”的问题)。这是一个 UseReducer 演示,尝试使用和不使用 StrictMode。它还 运行s 两次。好像也需要纯的:
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 严格模式时发生的,而且是有意为之。
如 this article 所述:
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):
- Class component constructor method
- The render method (includes function components)
- setState updater functions (the first argument)
- The static getDerivedStateFromProps lifecycle
- The React.useState state initializer callback function
- The React.useMemo callback
Checkout this codesandbox which logs to the console in hook callbacks and class methods to show you that certain things happen twice.
React 这样做是因为它无法可靠地警告您在这些方法中产生的副作用。但如果这些方法是幂等的,那么多次调用它们应该不会造成任何麻烦。如果它们不是幂等的,那么您应该注意到有趣的事情,希望您能够注意到并修复这些事情。
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.
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.
如上所示,包含此行为是为了帮助您查找代码中的错误。由于更新程序函数应该是幂等的和纯粹的,运行 它们两次而不是一次应该不会影响您的应用程序的功能。如果是这样,那就是您的代码中存在错误。
听起来您正在调用的 map.removeOverlays()
方法是一个有效的函数,因此不应在状态更新函数中调用它。根据
我是 运行 这段代码,默认情况下 useEffect() 在组件安装时最初被调用一次?
import React, { useState, useEffect } from 'react';
const ChangeDOMTitle = (props) => {
const [count, setCount] = useState(0);
const [name, setName] = useState('');
useEffect(() => {
console.log("Updating the count");
document.title = `Clicked ${count} times`;
});
return (
<React.Fragment>
<input type="text" value={name}
onChange={event => setName(event.target.value)} />
<button onClick={() => setCount(count + 1)}>Clicked {count} times</button>
</React.Fragment>
);
};
export default ChangeDOMTitle;