useEffect 的回调的返回语句何时执行?



我想澄清一下我对这里发生的事情的理解。任何细节可以提高我目前的理解将不胜感激。

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

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

当组件重新呈现时,任何useEffect都将分析其依赖项数组的更改。如果发生了更改,则将运行该效果回调。这些回调保证按照它们在组件中声明的顺序运行。例如,在下面,a将始终记录在b之前。

const App = () => {
const [num, setNum] = React.useState(0);
React.useEffect(() => {
setInterval(() => {
setNum(num => num + 1);
}, 1000);
}, []);
React.useEffect(() => {
console.log('a', num);
}, [num]);
React.useEffect(() => {
console.log('b', num);
}, [num]);
return num;
}
ReactDOM.render(<App />, document.querySelector('.react'));
<script crossorigin src="https://unpkg.com/react@16/umd/react.development.js"></script>
<script crossorigin src="https://unpkg.com/react-dom@16/umd/react-dom.development.js"></script>
<div class='react'></div>

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

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

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

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

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

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

功能组件在其状态更改后是否"卸除",这是否会导致先前的 useEffect 回调返回语句执行?

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

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

组件何时拆卸?

当其父级停止渲染它时。请参阅条件呈现。


你能帮我理解,一般或在我的问题中的例子中,useEffect的返回语句何时执行?

取决于 dep 数组:

  • 如果它是空的[],则在卸载时。
  • 如果它有依赖项[value1,value2],则依赖项更改(浅层比较)。
  • 如果它没有依赖项(没有第二个参数用于useEffect),则在每个渲染上运行。

请参阅后续问题使用效果深入/使用效果?

你几乎到了点子上,让我试着对此提供更清晰的说明

在我们深入研究之前,我们需要了解,如果useEffect没有第二个参数(就像在问题中一样),传递给useEffect的函数将在每次渲染中执行。

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

最新更新