`useEffect`触发两次引起的bug
- 写在前面
- 程序源码
- 出错原因分析
- strictMode导致组件两次加载
- 第一次拿到了,第二次没拿到,说明第一次拿到后出了问题
- 专业的把初始化放到`useReducer`里面
写在前面
今天在用react
+typescript
写todolist
的时候出现了一个奇怪的bug。那就是…
useEffect执行了两次
复制
- 一般来说,第二个参数给一个空数组,useEffect只会执行一次的
- 有图有真相!

- 在研究这个问题后得到了大概三种解决方案:
- 1.取消
react.strictMode
模式 - 2.在设置的参数的
useEffect
中加非空判断 - 3.把初始化放到
useReducer
里面
具体的分析在下面可以看到
程序源码
- 因为不是全部源码,下面的解说会帮你理解代码
- 1.init()是useReducer的第三个参数
- 2.
[state,dispatch]
是用来控制todoList
的相关操作的(这个不是本文重点) - 3.第一个
useEffect
就是第一次进入页面读取localStorage
的数据赋值给state.todoList - 4.第二个
useEffect
是state.todoList
变化后把它存储在localStorage
里面
| function init(initTodoList: ITodo[]): IState { |
| return { |
| todoList: initTodoList |
| } |
| } |
| const [state, dispatch] = useReducer(todoReducer, [], init) |
| useEffect(() => { |
| const todoList = JSON.parse(localStorage.getItem('todolist') ?? '[]') |
| console.log(todoList, 'useEffect') |
| dispatch({ |
| type: ACTION_TYPE.INIT_TODOLIST, |
| payload: todoList |
| }) |
| }, []) |
| useEffect(() => { |
| localStorage.setItem('todolist', JSON.stringify(state.todoList)) |
| }, [state.todoList]) |
复制
出错原因分析
- 错误图解如下

strictMode导致组件两次加载
- React18添加了
strictMode
,npx create-react-app
创建新项目默认是带有strictMode
严格模式的
- 这个模式会导致组件加载两次,如果我们关闭掉这个模式,就不会出错了
- 但是这并没有根除我们的bug,因为正常加载两次也是不会出错的
- 而且,退一步来说,新项目自带严格模式,说明它是有必要的,官网也提到了它的优点,这里不展开讨论了
| const root = ReactDOM.createRoot( |
| document.getElementById('root') as HTMLElement |
| ); |
| root.render( |
| <React.StrictMode> // 这里 |
| <App /> |
| </React.StrictMode> |
| ); |
复制
如果代码没有问题,加载两次应该是不会出错了,所以有了后面的解决办法的研究
第一次拿到了,第二次没拿到,说明第一次拿到后出了问题
- 也就是第二次
useEffect
那里出的问题
- 在第一个
useEffect
还没有完成初始化的时候,useReducer
触发了第二个useEffect
,导致了我们重新setItem导致了localStorage
中的todolist变成了空数组 - 自然在第二次取的时候就变成了空组数
- 所以我们加一个判断,非空的时候再改变就可以了
- 注意:这里的判断不能用
state.todoList===[]
| useEffect(() => { |
| if (state.todoList.length) |
| localStorage.setItem('todolist', JSON.stringify(state.todoList)) |
| }, [state.todoList]) |
复制
专业的把初始化放到useReducer
里面
| const [state, dispatch] = useReducer(todoReducer, JSON.parse(localStorage.getItem('todolist') ?? '[]'), init) |
复制