React Todo列表:页面刷新时localStorage被Context空数组覆盖求助
解决React Context Todo列表刷新数据丢失问题
问题根源
- Context的
useState初始值固定为空数组,页面刷新时会先以空数组作为初始状态,后续组件内的存储逻辑可能会将空数组写入localStorage,直接覆盖原有数据。 Form和List组件重复实现了读取/写入localStorage的逻辑,导致状态同步行为重复触发,加剧了数据覆盖的问题。
修复方案
1. 重构Context Provider,统一管理状态与存储逻辑
将localStorage的读取和写入逻辑集中在Context内部,避免组件重复处理,同时确保初始状态直接从localStorage加载:
import React from "react"; export const TodoContext = React.createContext(); export function TodoComponent({ children }) { // 初始化时优先读取localStorage中的数据,无数据则用空数组 const [todoItems, setTodoItems] = React.useState(() => { const savedTodos = localStorage.getItem("todos"); return savedTodos ? JSON.parse(savedTodos) : []; }); // 封装状态更新方法,自动同步到localStorage const updateTodoItems = (newTodos) => { setTodoItems(newTodos); localStorage.setItem("todos", JSON.stringify(newTodos)); }; return ( <TodoContext.Provider value={[todoItems, updateTodoItems]}> {children} </TodoContext.Provider> ); }
2. 简化Form组件,移除重复存储逻辑
组件仅专注于表单交互,状态更新直接调用Context提供的方法:
function Form() { const [todoItems, updateTodoItems] = useContext(TodoContext); const [todoInput, setTodoInput] = useState(""); const handleSubmit = (e) => { e.preventDefault(); const trimmedInput = todoInput.trim(); if (trimmedInput.length >= 1) { updateTodoItems([ ...todoItems, // 用时间戳生成唯一ID,避免数组长度变化导致ID重复 { id: Date.now(), name: trimmedInput }, ]); setTodoInput(""); } }; return ( <> <FormWrapper> <form action="" onSubmit={handleSubmit}> <input type="text" placeholder="Todo" value={todoInput} onChange={(e) => setTodoInput(e.target.value)} /> <input type="submit" value="添加" /> </form> </FormWrapper> </> ); }
3. 简化List组件,移除重复存储逻辑
组件仅负责渲染列表和删除操作:
function List() { const [todoItems, updateTodoItems] = useContext(TodoContext); const deleteButton = (e, id) => { e.preventDefault(); const newArray = todoItems.filter(item => item.id !== id); updateTodoItems(newArray); }; return ( <> <ListWrapper> <ul className="list"> {todoItems?.map((item) => ( <li className="list__item" key={item.id}> <div className="todo"> <span className="todo__label">{item.name}</span> <button className="todo__delete" onClick={(e) => deleteButton(e, item.id)} > 删除 </button> </div> </li> ))} </ul> </ListWrapper> </> ); }
额外优化说明
- 用
Date.now()生成Todo的唯一ID,比依赖数组长度更可靠,避免删除后新增项出现ID重复问题。 - 提交时对输入内容做
trim()处理,过滤纯空格的无效输入。
内容的提问来源于stack exchange,提问作者Sjoerd Vermeijden
相关产品推荐
相关产品推荐

