参考资料: https://youtu.be/hQAHSlTtcmY?t=1342

我遇到了一个问题,我无法从useEffectlocalStorage初始化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

在我看来,setTodos(storedTodos)并没有像预期的那样工作.解决方案是通过useState加载初始化,如下所示.

Question>有人能告诉我为什么这个初始化在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.使用您的注释代码来初始化状态变量.然后为setTodos创建一个包装器来缓存值.删除两个useEffect.

  const [todos, _setTodos] = useState(() => {
    const storedTodosString = localStorage.getItem(LOCAL_STORAGE_KEY);
    if (!storedTodosString) return [];
    return JSON.parse(storedTodosString);
  });

  function setTodos(todos) {
    if (typeof todos === 'function') {
      _setTodos((prevTodos) => {
        const newTodos = todos(prevTodos);
        if (!newTodos) return prevTodos;
        localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(newTodos));
        return newTodos;
      });
    } else {
      if (!todos) return;
      localStorage.setItem(LOCAL_STORAGE_KEY, JSON.stringify(todos));
      _setTodos(todos);
    }
  }

工作示例:https://stackblitz.com/edit/stackblitz-starters-awp3f5?file=src%2FApp.tsx


您的解决方案不起作用,因为状态变量在渲染期间是不可变的.在第一次渲染时,无论调用setTodos多少次,值todos都是[].这意味着您正在向init上的本地存储写入[].严格模式导致useEffects在一次渲染中被调用两次(在开发期间),因此操作顺序如下:

  1. 读取本地存储,使用存储值调用setTodos
  2. []写入本地存储
  3. 从本地存储读取[],使用[]调用setTodos
  4. []写入本地存储.

Reactjs相关问答推荐

NextJs Form不允许我输入任何输入,

404使用GitHub操作部署Next.js应用程序时出错

react 下拉菜单任务

如何使用Reaction Testing Library+Vitest正确更新单元测试中的输入?

子路径上未定义useMatches句柄

设置自定义形状的纹理

数据表格组件需要所有行都具有唯一的ID属性.或者,您可以使用getRowId属性.

React中的功能性组件克隆优化

BrowserRouter改变了URL但没有链接到页面

React - 拖放 UML

React Three Fiber mesh 标签在此浏览器中无法识别

使用dayjs时如何在输入类型日期时间本地字段中设置默认值?

MUI - ButtonGroup fullWidth 条件屏幕大小不起作用?

父状态改变后子组件丢失状态

如何解决在 react.js 中找不到模块错误

如何制作基于对象 struct 呈现数据的可重用组件?

setState 改变对象引用

如何修复 Next.js 13 中类型 AppRouterInstance 上不存在 x?

用 scss 覆盖 MUI

CORS 政策:无访问控制允许来源-AWS 和 Vercel