React.useCallback依赖state更新时未更新函数实现问题排查
问题原因分析
你遇到的confirmTodo始终读取初始状态的问题,核心有两个直接诱因:
- 首先是
updateTodo的实现存在闭包陷阱:你当前注释的setTodo({...todo, ...todoFragment})写法依赖了当前渲染闭包中的todo变量,同时你将todo加入了useCallback的依赖数组,这会导致每次todo更新时updateTodo都会生成新的函数实例。如果你的表单组件没有正确响应updateTodo的变化(比如挂载时绑定的事件回调仅捕获了初始版本的updateTodo),就会一直调用最初的updateTodo,而这个初始版本闭包里的todo永远是初始值,最终导致状态根本没有正常更新,confirmTodo自然读取不到最新的todo。 - 其次是
confirmTodo的依赖数组存在遗漏:你用到了来自AllTodosContext的addTodo,但没有将其加入依赖数组,虽然这不是你当前问题的直接原因,但会导致后续添加多条待办时出现异常。
另外还有两个隐藏问题需要修复:
AllTodosProvider中todos的初始状态为空,没有设置默认空数组,第一次调用addTodo时扩展undefined会报错。NewTodoContext.Provider的value每次渲染都会生成新的对象,会导致所有消费该context的组件不必要重渲染。
修复方案
1. 修复NewTodoProvider逻辑
将updateTodo改为函数式状态更新,避免依赖todo,同时补全confirmTodo的依赖,用useMemo缓存context的value:
function NewTodoProvider({ children }){ const [todo, setTodo] = React.useState(DEFAULT_EMPTY_TODO_STATE); const [ addTodo ] = React.useContext(AllTodosContext); const updateTodo = React.useCallback((todoFragment) => { // 改用函数式更新,不需要依赖外层todo setTodo(prevTodo => ({...prevTodo, ...todoFragment})) }, []); // 补全addTodo依赖 const confirmTodo = React.useCallback(() => { addTodo(todo) }, [todo, addTodo]); // 缓存context value避免不必要重渲染 const contextValue = React.useMemo(() => ({ todo, updateTodo, confirmTodo }), [todo, updateTodo, confirmTodo]) return ( <NewTodoContext.Provider value={contextValue}> { children } </NewTodoContext.Provider> ) }
2. 修复AllTodosProvider逻辑
给todos设置初始空数组,同时将addTodo改为函数式更新,避免依赖todos,减少不必要的函数实例更新:
function AllTodosProvider({ children }){ // 加初始空数组 const [todos, setTodos] = React.useState([]); // 改用函数式更新,不需要依赖外层todos const addTodo = React.useCallback((newTodo) => { setTodos(prevTodos => [...prevTodos, newTodo]); },[]); const contextValue = React.useMemo(() => ({ todos, addTodo }), [todos, addTodo]) return ( <AllTodosContext.Provider value={contextValue}> { children } </AllTodosContext.Provider> ) }
内容的提问来源于stack exchange,提问作者alchemist95
相关产品推荐
相关产品推荐

