React中localStorage页面刷新后数据丢失问题排查
问题根因
你遇到的刷新后数据丢失问题,本质是useEffect执行时序和React状态异步更新的竞态问题导致的:
- 组件首次渲染时,
boxes会被直接初始化为硬编码的示例Box 1数组 - 渲染完成后,负责写入localStorage的
useEffect会在读取localStorage的逻辑完成状态更新前,先把初始的示例数组写入localStorage,直接覆盖掉用户之前存储的自定义内容 - 如果你用的是React 18+版本且开发环境开启了严格模式,组件会执行挂载->卸载->重新挂载的双调用流程,这个覆盖问题会稳定复现:第一次挂载时写副作用已经把localStorage覆盖成初始示例数组,第二次挂载时读副作用只能读到被覆盖后的示例数据,最终刷新后永远只显示Box 1。
- 额外体验问题:把读localStorage的逻辑放在空依赖的
useEffect里,会导致首屏先渲染默认的Box 1,再跳转到实际存储的内容,出现内容闪烁。
修复方案
直接调整状态初始化逻辑,移除多余的读副作用,从根源避免竞态:
- 利用
useState的懒初始化能力,组件首次创建状态时直接从localStorage读取存储值,没有已存数据时才返回默认示例数组 - 删除单独用来读localStorage的
useEffect,只保留监听boxes变化同步写入localStorage的副作用即可 - 修正代码首行手滑打错的导入语句(原代码第一行
mport缺了首字母i)
修复后的完整代码如下:
import React, { Fragment, useState, useRef, useEffect } from 'react'; import { v4 as uuidv4} from 'uuid'; import { BoxList } from './components/BoxList'; import { SearchBox } from './components/SearchBox'; const KEY = "boxApp.boxes"; export function App() { // 懒初始化状态:仅首次挂载时执行一次读localStorage逻辑 const [boxes, setBoxes] = useState(() => { const storedData = localStorage.getItem(KEY); return storedData ? JSON.parse(storedData) : [ {id: 1, title: 'Box 1', content: 'This is just an example of a box, remove it and create yours!' } ]; }) const boxTitleRef = useRef(); const boxContentRef = useRef(); // 仅在boxes变化时同步写入localStorage useEffect(() =>{ localStorage.setItem(KEY, JSON.stringify(boxes)); }, [boxes]); const removeBox = (id) => { const remainingBoxes = boxes.filter((box) => box.id !== id); setBoxes(remainingBoxes); } const addNewBox = (evt) => { evt.preventDefault(); const boxTitle = boxTitleRef.current.value; const boxContent = boxContentRef.current.value; if (boxTitle === '' || boxContent === '') return; setBoxes( previousBoxes => { return ([...previousBoxes, { id: uuidv4(), title: boxTitle, content: boxContent}]) }); boxTitleRef.current.value = null; boxContentRef.current.value = null; } return ( <Fragment> <SearchBox /> <div> <h2>Add a box</h2> <form> <input type='text' placeholder='Title' ref={boxTitleRef}></input> <input type='text' placeholder='Content' ref={boxContentRef}></input> <button type='submit' onClick={addNewBox}>➕</button> </form> </div> <BoxList boxes={boxes} removeBox={removeBox}/> </Fragment> ); } export default App;
修复逻辑说明
- 状态懒初始化只会在组件首次挂载时执行一次,不会出现硬编码初始值提前覆盖localStorage的问题,同时首屏会直接渲染存储的内容,不会出现默认内容闪烁
- 移除多余的读副作用后,不存在两个副作用的执行顺序竞态,所有对
boxes的增删操作都会自动同步到localStorage,刷新页面后数据不会丢失。
内容的提问来源于stack exchange,提问作者TaFran
相关产品推荐
相关产品推荐

