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

React Todo List本地存储失效排查:使用localStorage后待办项刷新丢失问题求助

问题分析与解决方案

你遇到的问题核心在于组件初始挂载时,第二个useEffect会将空数组写入localStorage,直接覆盖掉之前存储的待办事项。

具体来说:

  • 组件初始化时,todos的初始值是[]
  • 第二个useEffect依赖todos,所以会在组件挂载后立即执行,把空数组存入localStorage
  • 第一个useEffect虽然会读取localStorage里的旧数据,但setTodos是异步的,还没等状态更新,第二个useEffect已经把空数组覆盖进去了,导致下次刷新时读不到旧数据

另外,你的toggleTodo函数直接修改了原数组里的对象,虽然能触发更新,但不符合React的不可变数据原则,可能会引发一些难以排查的问题,也建议一起优化。


修复步骤

1. 阻止初始挂载时的localStorage写入

我们可以添加一个状态变量标记是否是首次渲染,让第二个useEffect只在todos真正发生变更时才执行保存操作:

import React, { useState, useRef, useEffect } from "react";
import TodoList from "./TodoList"
const { v4: uuidv4 } = require('uuid');
const LOCAL_STORAGE_KEY = 'todoApp.todos'

function App() {
  const [todos, setTodos] = useState([])
  const doAccess = useRef()
  // 标记是否为首次渲染
  const [isFirstRender, setIsFirstRender] = useState(true)

  useEffect(() => {
    const storedTodos = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY))
    if (storedTodos) setTodos(storedTodos)
  }, [])

  useEffect(() => {
    // 首次渲染时跳过保存
    if (isFirstRender) {
      setIsFirstRender(false)
      return
    }
    localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos))
  }, [todos, isFirstRender])

  // 优化toggleTodo,遵循不可变数据原则
  function toggleTodo(id){
    setTodos(prevTodos => 
      prevTodos.map(todo => 
        todo.id === id ? {...todo, complete: !todo.complete} : todo
      )
    )
  }

  function handleAddTodo(e){
    const name= doAccess.current.value
    if (name === '') return
    setTodos (prevTodos =>{
      return [...prevTodos, {id: uuidv4(), name: name, complete: false}]
    })
    // 清空输入框
    doAccess.current.value = ''
  }

  // 补充Clear Todo功能
  function handleClearTodos() {
    setTodos(prevTodos => prevTodos.filter(todo => !todo.complete))
  }

  return (
    <>
      <TodoList todos={todos} toggleTodo={toggleTodo}/>
      <input ref={doAccess} type="text" />
      <button onClick={handleAddTodo}>Add Todo</button>
      <button onClick={handleClearTodos}>Clear Completed Todos</button>
      <div>{todos.filter(todo => !todo.complete).length} Todos Left</div>
    </>
  )
}

export default App;

2. 额外优化点

  • 优化了toggleTodo函数,使用map创建新数组和新的todo对象,完全遵循不可变数据原则,避免直接修改原状态
  • 补充了Clear Completed Todos的功能实现
  • 添加了清空输入框的逻辑,提升用户体验
  • 修复了剩余待办数量的计算逻辑

内容的提问来源于stack exchange,提问作者ipshita sett

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 19:52:32