更新React Context值时useEffect钩子为何不触发?
问题描述
在分步向导中使用React Context收集用户数据,最终在最后一步处理这些数据。Person字段可正常更新且后续页面能获取,但输入姓名和地址后,尝试通过useEffect调用API做处理时,useEffect钩子完全不触发。尝试依赖personState或其属性的写法均无效。
原代码示例
export interface Person { name: string; address: string; skills: string[]; // 在向导的其他页面收集 }; const PersonContext = React.createContext(null); const PersonProvider: React.FC = ({children}) => { const [personState, setPersonState] = useState<Person>({ name: "", address: "" }); return ( <PersonContext.Provider value={personState}> {children} </PersonContext.Provider> ); }; export const PersonPage: React.FC<{}> = () => { const personState = useContext(PersonContext); useEffect(() => { console.log("useEffect"); }, [personState]); /* 也试过这种写法,同样无效: useEffect(() => { console.log("useEffect"); }, [personState.name, personState.address]); */ const onNameChanged = (e) => { if(event.key === "Enter") { console.log(`name: ${e.target.value}`); personState.name = e.target.value; } }; const onAddressChanged = (e) => { if(event.key === "Enter") { console.log(`name: ${e.target.value}`); personState.address = e.target.value; } }; return ( <div> Name: <input name="name" onKeyDown={(e) => onNameChanged(e)} /> (按回车确认) <br /> 你输入的是: <span>{personState.name}</span> <br/> <br/> Address: <input name="address" onKeyDown={(e) => onAddressChanged(e)} /> (按回车确认) <br /> 你输入的是: <span>{personState.address}</span> </div> ); }; export const App: React.FC<{}> = () => { return ( <PersonProvider> <PersonPage /> </PersonProvider> ); }
问题原因
核心问题是直接修改了state对象的属性,没有使用React提供的setPersonState更新函数。React的状态更新依赖不可变原则,直接修改对象引用不会触发组件重新渲染,useEffect的依赖对比也不会检测到变化,因此钩子不会执行。另外原代码存在事件变量未定义的错误:事件处理函数中使用了未声明的event,应该用参数e代替。
修复方案
- 在
PersonProvider中,将setPersonState加入Context的value中,让子组件可以调用状态更新方法。 - 修改事件处理函数,通过
setPersonState以不可变的方式更新状态。 - 修正事件参数的错误。
修改后的完整代码
export interface Person { name: string; address: string; skills: string[]; // 在向导的其他页面收集 }; // 更新Context类型,包含state和更新方法 const PersonContext = React.createContext<{ personState: Person; setPersonState: React.Dispatch<React.SetStateAction<Person>>; } | null>(null); const PersonProvider: React.FC = ({children}) => { const [personState, setPersonState] = useState<Person>({ name: "", address: "", skills: [] }); return ( <PersonContext.Provider value={{ personState, setPersonState }}> {children} </PersonContext.Provider> ); }; export const PersonPage: React.FC<{}> = () => { const { personState, setPersonState } = useContext(PersonContext) || {}; useEffect(() => { console.log("useEffect触发了"); // 这里可以调用API处理数据 }, [personState.name, personState.address]); const onNameChanged = (e: React.KeyboardEvent<HTMLInputElement>) => { if(e.key === "Enter") { const value = e.currentTarget.value; console.log(`name: ${value}`); // 不可变更新state setPersonState(prev => ({ ...prev, name: value })); } }; const onAddressChanged = (e: React.KeyboardEvent<HTMLInputElement>) => { if(e.key === "Enter") { const value = e.currentTarget.value; console.log(`address: ${value}`); // 不可变更新state setPersonState(prev => ({ ...prev, address: value })); } }; if (!personState || !setPersonState) return null; return ( <div> Name: <input name="name" onKeyDown={onNameChanged} /> (按回车确认) <br /> 你输入的是: <span>{personState.name}</span> <br/> <br/> Address: <input name="address" onKeyDown={onAddressChanged} /> (按回车确认) <br /> 你输入的是: <span>{personState.address}</span> </div> ); }; export const App: React.FC<{}> = () => { return ( <PersonProvider> <PersonPage /> </PersonProvider> ); }
内容的提问来源于stack exchange,提问作者Kev
相关产品推荐
相关产品推荐

