You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 12:18:15