为何在useEffect中从localStorage初始化todos失败?
问题:为何useEffect中初始化localStorage的todos状态不生效?
我尝试通过useEffect从localStorage初始化todos状态,但添加两个待办项后刷新浏览器,所有待办项都无法加载回来。控制台日志如下:
storedTodos size: 2 App.js:41 todos size: 0 App.js:34 storedTodos size: 0 App.js:41 todos size: 0 App.js:41 todos size: 0
目前发现通过useState初始化(代码中已注释)可以解决问题,但不清楚为何useEffect的方式无法生效。
相关代码:
function App() { const [todos, setTodos] = useState([]) // const [todos, setTodos] = useState(() => { // if (localStorage.getItem(LOCAL_STORAGE_KEY)) { // const storedTodos = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) // return storedTodos; // } // else // return []; // }) const todoNameRef = useRef() // This doesn't work as expected // useEffect(() => { const storedTodos = JSON.parse(localStorage.getItem(LOCAL_STORAGE_KEY)) if (storedTodos) { console.log("storedTodos size: ", storedTodos.length) setTodos(storedTodos) } }, []) useEffect(() => { console.log("todos size: ", todos.length) localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos)) }, [todos]) function handleAddTodo(e) { const name = todoNameRef.current.value if (name === '') return setTodos(prevTodos => { return [...prevTodos, { id: uuidv4(), name: name, complete: false }] }) todoNameRef.current.value = null } return ( <> <TodoList todos={todos} /> <input ref={todoNameRef} type="text" /> <button onClick={handleAddTodo}>Add Todo</button> <button>Clear Completed Todos</button> <div>0 left to do</div> </> ) } export default App;
原因分析
问题出在两个useEffect的执行顺序和React严格模式的副作用检测机制上:
- 初始渲染的异步状态更新:组件挂载时,第一个空依赖的useEffect先执行,读取localStorage拿到2条待办数据后调用
setTodos(storedTodos)。但React的状态更新是异步的,此时todos仍保持初始值[]。 - localStorage被提前覆盖:紧接着,依赖
todos的第二个useEffect触发(因为初始todos是[]),直接把空数组存回localStorage,覆盖了原本的有效数据。 - 严格模式的重复执行:在React严格模式下,挂载阶段的副作用函数(空依赖useEffect)会被执行两次。第二次执行时,localStorage已经被清空,读取到的
storedTodos为空,自然无法恢复数据。
而用useState初始化函数的方式,是在组件初始化阶段同步执行的,此时第二个useEffect还未触发,不会出现localStorage被覆盖的问题,因此能正常加载数据。
内容的提问来源于stack exchange,提问作者q0987
相关产品推荐
相关产品推荐

