React中useEffect多次调用导致localStorage持久化状态刷新重置
问题根因
你的代码出问题是两个原因共同导致的:
- React 18 开发环境严格模式的重复挂载逻辑:如果你的项目入口用
<React.StrictMode>包裹了根组件,开发环境下React会主动执行一次组件挂载→卸载→重新挂载的流程,用来检测副作用是否缺少清理逻辑,这就是你看到init日志打印两次的来源。 - 副作用执行顺序导致存储值被覆盖:组件首次渲染时
counter的初始值是0,你写的第二个监听counter变化的useEffect会在挂载后立刻执行,此时第一个读取localStorage的副作用还没完成状态更新,就先把初始值0写入localStorage,把之前存的4覆盖掉了。等第一个副作用读到旧值4的时候,存储里的值已经变成0,最终状态就被重置成0了。
修复方法
不要把读取初始存储值的逻辑放到挂载后的useEffect里,直接在useState初始化的时候读取存储值,从根源避免初始值0提前写入覆盖旧数据,修改后的代码如下:
import React, { useState, useEffect } from 'react' const Test = () => { // 初始化时直接读localStorage,函数只会在组件首次渲染时执行一次 const [counter, setCounter] = useState(() => { const storedVal = localStorage.getItem('counter') const parsed = parseInt(storedVal, 10) // 处理存储值不存在、格式非法的兜底场景 return isNaN(parsed) ? 0 : parsed }) useEffect(() => { localStorage.setItem('counter', counter) console.log('changed', counter) }, [counter]) const addCounter = () => { setCounter((c) => c + 1) // 此处打印的是点击时闭包捕获的旧状态,不是更新后的值,属于React的正常特性 console.log('added', counter) } return ( <div> {counter} <button onClick={addCounter}>+</button> </div> ) } function App() { return ( <div className='App'> <Test /> </div> ) }
修改后不管是否开启严格模式都不会出现值被重置的问题:首次渲染就直接拿到存储里的历史值作为初始状态,后续只有counter真正变化时才会写入新值到存储。
补充:如果你确实不想在开发环境看到严格模式的重复执行日志,可以移除根组件外的
<React.StrictMode>包裹,但更推荐保留严格模式,它能帮你提前排查很多副作用相关的潜在bug。
内容的提问来源于stack exchange,提问作者masroore
相关产品推荐
相关产品推荐

