useEffect触发组件卸载时父组件对象状态丢失问题排查
问题原因与解决方案
你遇到的这个问题其实是React中典型的闭包陷阱,和useEffect的依赖机制直接相关,我来帮你拆解清楚:
为什么会出现这个问题?
你在子组件里用了useEffect(() => () => saveName(), []);,这里的空依赖数组意味着这个effect只会在组件挂载时执行一次,它返回的清理函数(也就是你用来模拟componentWillUnmount的部分)会捕获挂载时的所有变量引用。
具体来说:
- 挂载时,
name、firstName、birthdate都是初始的空对象(比如{name: ""}) saveName函数也是父组件初始渲染时创建的版本,它里面引用的状态都是初始值- 当后续你修改表单更新状态时,父组件会重新渲染,生成新的
saveName函数和新的状态,但子组件的effect因为依赖是空数组,不会重新执行,所以清理函数里的saveName还是旧版本,自然只能拿到初始的空对象
解决方案(保留对象形式状态)
我们可以通过让effect的清理函数捕获最新的状态/函数来解决这个问题,这里有两种常用的方案:
方案一:用useCallback包裹saveName并更新effect依赖
在父组件中,用useCallback把saveName包裹起来,让它的依赖是name、firstName、birthdate,这样只有当这些状态变化时,saveName才会重新创建。然后子组件的useEffect依赖数组加上saveName,确保清理函数能拿到最新的saveName。
修改后的父组件代码:
import React, { useCallback } from "react"; import ChildComponent from "./ChildComponent"; const ParentComponent = () => { const [childKilled, setChildKilled] = React.useState(false); const [name, setName] = React.useState({ name: "" }); const [firstName, setFirstName] = React.useState({ firstName: "" }); const [birthdate, setBirthdate] = React.useState({ birthdate: "" }); // 用useCallback包裹,依赖为三个状态对象 const saveName = useCallback(() => { console.log(name); console.log(firstName); console.log(birthdate); // REST-Call }, [name, firstName, birthdate]); return ( <div> {!childKilled && ( <ChildComponent name={name} firstName={firstName} birthdate={birthdate} setFirstName={setFirstName} setBirthdate={setBirthdate} setName={setName} saveName={saveName} setChildKilled={setChildKilled} /> )} </div> ); }; export default ParentComponent;
子组件修改useEffect的依赖:
useEffect(() => () => saveName(), [saveName]);
方案二:用useRef保存最新状态
另一种方式是在父组件中用useRef来同步最新的状态,这样不管组件怎么渲染,ref里始终保存着最新的状态值,清理函数里直接访问ref就能拿到最新数据。
父组件修改示例:
import React, { useRef, useEffect } from "react"; import ChildComponent from "./ChildComponent"; const ParentComponent = () => { const [childKilled, setChildKilled] = React.useState(false); const [name, setName] = React.useState({ name: "" }); const [firstName, setFirstName] = React.useState({ firstName: "" }); const [birthdate, setBirthdate] = React.useState({ birthdate: "" }); // 创建ref保存最新状态 const latestStateRef = useRef({ name, firstName, birthdate }); // 每次状态更新时同步ref useEffect(() => { latestStateRef.current = { name, firstName, birthdate }; }, [name, firstName, birthdate]); const saveName = () => { // 从ref里拿最新状态 const { name, firstName, birthdate } = latestStateRef.current; console.log(name); console.log(firstName); console.log(birthdate); // REST-Call }; return ( <div> {!childKilled && ( <ChildComponent name={name} firstName={firstName} birthdate={birthdate} setFirstName={setFirstName} setBirthdate={setBirthdate} setName={setName} saveName={saveName} setChildKilled={setChildKilled} /> )} </div> ); }; export default ParentComponent;
这种方式下,子组件的useEffect不需要修改依赖,因为saveName里访问的是ref的current属性,始终是最新的。
总结
两种方案都能解决你的问题,方案一更符合React的hooks规范,推荐优先使用;方案二则适合不想频繁更新回调函数的场景。两种方式都能保留你需要的对象形式状态。
内容的提问来源于stack exchange,提问作者yemerra
相关产品推荐
相关产品推荐

