useEffect 的回调的 return 语句什么时候执行?

IT技术 javascript reactjs react-hooks
2021-04-21 21:06:28

我想澄清我对这里发生的事情的理解。任何改善我目前理解的细节将不胜感激。

function Timer() {

    let [time, setTime] = useState(5);

    useEffect(() => {
        let timer = setInterval(() => {
          setTime(time - 1);
        }, 1000)
        return () => clearInterval(timer);
    }, );

    return <div>{time}</div>
}

export default Timer

https://codesandbox.io/s/cranky-chaplygin-g1r0p

  1. time正在初始化为5.
  2. useEffect已读。它的回调必须准备好稍后触发。
  3. div呈现。
  4. useEffect的回调被执行。setInterval的回调准备好触发。当然,这里不会触发useEffect'sreturn语句,因为如果触发它会取消计时器(并且计时器确实有效)。
  5. 大约 1 秒后,setInterval的回调触发将time( 到 4)的状态更改
  6. 现在一个状态发生了变化,函数被重新执行。time,一个新变量,被初始化为新的时间状态。
  7. useEffect读取一个新的,它的回调准备稍后触发。(发生这种情况是因为没有 的第二个参数useEffect())。
  8. 组件函数的return语句被执行。这有效地重新渲染了div.
  9. 在某些时候,前一个useEffectreturn语句会执行(这会禁用timer前一个中的useEffect)。我不确定什么时候会发生这种情况。
  10. 'new'useEffect的回调被执行。
3个回答

功能组件在其状态发生变化后是否会“卸载”,这是否会导致之前的 useEffect 回调返回语句的执行?

不,组件在其生命周期结束时仅卸载一次,React 允许useEffect通过提供一个带有return语句的空 dep 数组来执行带有钩子的回调

useEffect(() => {
  return () => {
    console.log("unmounts");
  };
}, []);

组件何时卸下?

当它的父级停止渲染它时。请参阅条件渲染


你能帮我理解,一般来说还是在我的问题中的例子中,useEffect 的 return 语句何时执行?

取决于 dep 数组:

  • 如果它是空的[],则卸载。
  • 如果它有依赖项[value1,value2],则依赖项会发生变化(浅比较)。
  • 如果它没有依赖项(没有第二个参数useEffect),它会在每次渲染时运行。

请参阅后续问题useEffect 深入/useEffect 的使用?

谢谢@DennisVash。我已经更新了我的问题,以表明我对代码中发生的事情的理解,现在我理解useEffect得更好了。我不确定我useEffect的 return 语句何时执行。你说它在每次渲染上运行。初始执​​行/渲染是否包含在其中?当然,这个想法的问题是,useEffect'sreturn会在计时器的回调有机会执行之前立即取消计时器(并且计时器确实有效)。
2021-06-14 21:06:28

你对事件顺序的理解是正确的。唯一缺少的是效果回调和清理的精确时间。

当组件重新渲染时,任何useEffects 都将分析其依赖数组以进行更改。如果有变化,那么效果回调将运行。这些回调保证按照它们在组件中声明的顺序运行例如,下面,a将始终在 之前记录b

这些效果回调将在浏览器重新绘制后不久运行

现在将效果清理回调添加到组合中。这些将始终渲染运行的效果回调之前同步运行。例如,假设组件从 Render A 开始,并且在 Render A 中,效果挂钩返回了清理回调。然后,一些状态发生变化,并发生到渲染 B 的转换,并且存在一个useEffect包含状态变化的依赖数组。将会发生的事情是:

  • 功能组件将使用新的props/状态调用,用于渲染 B
  • 组件在函数结束时返回新标记
  • 如有必要,浏览器会重新绘制屏幕
  • 来自渲染 A清理功能将运行
  • 来自渲染 B效果回调将运行

您可以在此处查看最后两个操作的源代码

commitHookEffectListUnmount(Passive$1 | HasEffect, finishedWork);
commitHookEffectListMount(Passive$1 | HasEffect, finishedWork);

第一次调用调用来自先前渲染的所有清理回调。第二个调用调用当前渲染的所有效果回调。当前渲染效果回调在执行先前的渲染清理回调之后同步运行。

我也明白(虽然无可否认,只是一般情况下),在处理(功能性)组件及其子组件时,子组件的useEffect(常规)回调将在父组件之前执行。在“清理”回调方面,我可以期待类似的行为吗?例如,子组件的清理回调将在其父组件的清理回调之前执行。这可能不是有用的信息(请随意证明我是错的),但我想我也可以理解。
2021-05-29 21:06:28
此外,在清理回调中,它们按源顺序执行?
2021-06-04 21:06:28
是的,这就是它的工作原理。先前渲染运行的所有清理(按效果声明顺序),然​​后是当前渲染运行的所有效果回调(按效果声明顺序)。
2021-06-11 21:06:28
所以RenderA的清理回调总是在RenderB的常规回调之前执行useEffect即使useEffect1,包含在 renderB 上看到的常规回调,在源顺序上高于useEffect2(在 renderB 上,只有清理回调要执行),useEffect2清理回调将在useEffect1常规回调之前执行
2021-06-15 21:06:28

你几乎说到点子上了,让我试着更清楚地说明这一点

在我们深入研究之前,我们需要了解如果useEffect没有任何东西作为第二个参数(正如问题中的那样),传递给的函数useEffect将在每次渲染中执行。

  1. 传递给的参数useState()用作初始值。因此时间被初始化为 5
  2. useEffect被执行 =>setTimeout()现在将在 1 秒后执行 =>useEffect调用它的返回值func1 被存储以供稍后执行
  3. 时间的值,现在是 5,被渲染
  4. 1 秒后setTimeout()执行并将时间的值更改为 4 并设置它
  5. 因此会发生重新渲染并useEffect再次执行。此时useEffect执行func1来清理之前的效果,然后传递给的函数useEffect会执行,所以setTimeout()会初始化一个new并返回语句,让调用这个func2存起来待会儿执行
  6. 时间的值,现在是 4,被渲染
  7. 1 秒后setTimeout()执行并将时间值更改为 3 并设置它
  8. 现在回到第 4 点,这个过程一直在无限发生