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

为何在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严格模式的副作用检测机制上:

  1. 初始渲染的异步状态更新:组件挂载时,第一个空依赖的useEffect先执行,读取localStorage拿到2条待办数据后调用setTodos(storedTodos)。但React的状态更新是异步的,此时todos仍保持初始值[]。
  2. localStorage被提前覆盖:紧接着,依赖todos的第二个useEffect触发(因为初始todos是[]),直接把空数组存回localStorage,覆盖了原本的有效数据。
  3. 严格模式的重复执行:在React严格模式下,挂载阶段的副作用函数(空依赖useEffect)会被执行两次。第二次执行时,localStorage已经被清空,读取到的storedTodos为空,自然无法恢复数据。

而用useState初始化函数的方式,是在组件初始化阶段同步执行的,此时第二个useEffect还未触发,不会出现localStorage被覆盖的问题,因此能正常加载数据。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.16 00:27:48