React严格模式下如何避免useReducer搭配useEffect触发两次?
React Strict Mode下useEffect触发两次导致useReducer幂等性失效的解决办法
React 18的Strict Mode在开发环境中会重复执行useEffect等钩子,目的是检测代码中的非幂等副作用。你遇到的问题是:用useState时可以通过状态判断实现幂等,但useReducer的dispatch会在两次useEffect执行完成后才批量生效,导致状态被更新两次。
问题差异分析
useState示例为什么有效
你的useState代码中,useEffect依赖了counter状态:
- 第一次
useEffect触发时,counter为0,执行setCounter(1),状态更新后组件重新渲染。 - Strict Mode触发第二次
useEffect时,counter已经是1,不满足counter === 0的条件,所以不会重复更新状态,最终结果为1,符合预期。
useReducer示例为什么失效
你的useReducer代码中,useEffect没有添加依赖项:
- 两次
useEffect触发时,都读取到初始的state.counter = 0,因此两次都执行了dispatch({ type: "increment" })。 - React在Strict Mode下会推迟
dispatch的执行(批量更新机制),两次dispatch最终都生效,导致counter变成2,幂等性失效。
解决办法
方法1:给useEffect添加正确的依赖项
将state.counter加入useEffect的依赖数组,这样第二次执行useEffect时,状态已经更新,条件不满足就不会重复dispatch:
const { StrictMode, useEffect, useReducer } = React; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <StrictMode> <WithUseReducer /> </StrictMode> ); function reducer(state, action) { switch (action.type) { case "increment": return { ...state, counter: state.counter + 1 }; default: throw new Error(); } } function WithUseReducer() { const [state, dispatch] = useReducer(reducer, { counter: 0 }); useEffect(() => { if (state.counter === 0) { dispatch({ type: "increment" }); } }, [state.counter]); // 添加state.counter作为依赖 return <React.Fragment>{state.counter}</React.Fragment>; }
<div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
方法2:使用ref标记执行状态
通过useRef创建一个标记,确保副作用只执行一次,不受Strict Mode重复触发的影响:
const { StrictMode, useEffect, useReducer, useRef } = React; const root = ReactDOM.createRoot(document.getElementById("root")); root.render( <StrictMode> <WithUseReducer /> </StrictMode> ); function reducer(state, action) { switch (action.type) { case "increment": return { ...state, counter: state.counter + 1 }; default: throw new Error(); } } function WithUseReducer() { const [state, dispatch] = useReducer(reducer, { counter: 0 }); const hasExecuted = useRef(false); useEffect(() => { if (!hasExecuted.current && state.counter === 0) { dispatch({ type: "increment" }); hasExecuted.current = true; } }, [state.counter]); return <React.Fragment>{state.counter}</React.Fragment>; }
<div id="root"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.2.0/umd/react.development.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.2.0/umd/react-dom.development.js"></script>
注意事项
Strict Mode的重复执行是开发环境独有的行为,生产环境不会出现。它的目的是帮你提前发现副作用中的非幂等问题,所以最好的实践是让你的副作用本身具备幂等性,而不是单纯规避重复执行。
内容的提问来源于stack exchange,提问作者Anemoia
相关产品推荐
相关产品推荐

