作为React开发者,你能答上如下两个问题么:
function Child() {
useEffect(() => {
console.log('child');
}, [])
return <p>hello</p>;
}
function Parent() {
useEffect(() => {
console.log('parent');
}, [])
return <Child/>;
}
function App() {
useEffect(() => {
console.log('app');
}, [])
return <Parent/>;
}
渲染<App/>时控制台的打印顺序是?
// componentDidMount生命周期钩子
class App extends React.Component {
componentDidMount() {
console.log('hello');
}
}
// 依赖为[]的useEffect
useEffect(() => {
console.log('hello');
}, [])
答案:
?向右滑动翻看答案 1. child -> parent -> app
2. 不同
其实,这两个问题分别考察的是:
useEffect的执行顺序useEffect如何介入React工作流程本文接下来将深入源码,带你了解这些知识。
这,就是关于useEffect的一切。
React的源码可以拆分为三块:
其中,只有渲染器会执行渲染视图操作。
对于浏览器环境来说,只有渲染器会执行类似appendChild、insertBefore这样的DOM操作。
协调器如何决定更新的内容呢?
答案是:他会为需要更新的内容对应的fiber(可以理解为虚拟DOM)打上标记。
这些被打标记的fiber会形成一条链表effectList。
渲染器会遍历effectList,执行标记对应的操作。
Placement标记对应插入DOMUpdate标记对应更新DOM属性useEffect也遵循同样的工作原理:
FunctionComponent被执行,执行到useEffect时会判断他的第二个参数deps是否有变化。deps变化,则useEffect对应FunctionComponent的fiber会被打上Passive(即:需要执行useEffect)的标记。渲染器中,遍历effectList过程中遍历到该fiber时,发现Passive标记,则依次执行该useEffect的destroy(即useEffect回调函数的返回值函数)与create(即useEffect回调函数)。其中,前两步发生在协调器中。
所以,effectList构建的顺序就是useEffect的执行顺序。
协调器的工作流程是使用遍历实现的递归。所以可以分为递与归两个阶段。
我们知道,递是从根节点向下一直到叶子节点,归是从叶子节点一路向上到根节点。
effectList的构建发生在归阶段。所以,effectList的顺序也是从叶子节点一路向上。
useEffect对应fiber作为effectList中的一个节点,他的调用逻辑也遵循归的流程。
现在,我们有充足的知识回答第一个问题:
由于归阶段是从Child到Parent到App,所以相应effectList也是同样的顺序。
所以useEffect回调函数执行也是同样的顺序。
我们在初学hook时,会用ClassComponent的生命周期钩子类比hook的执行时机。
即使官网也是这样教学的。
但是,从上文我们已经知道,React的执行遵循:
调度 -- 协调 -- 渲染
渲染相关工作原理是按照:
构建effectList -- 遍历effectList执行对应操作
整个过程都和生命周期钩子没有关系。
事实上生命周期钩子只是附着在这一流程上的钩子函数。
所以,更好的方式是从React运行流程来理解useEffect的执行时机。
按照流程,effectList会在渲染器中被处理。
对于useEffect来说,遍历effectList时,会找到的所有包含Passive标记的fiber。
依次执行对应useEffect的destroy。
所有destroy执行完后,再依次执行所有create。
整个过程是在页面渲染后异步执行的。
回答第二个问题:
如果useEffect的deps为[],由于deps不会改变,对应fiber只会在mount时被标记Passive。
这点是类似componentDidMount的。
但是,处理Passive effect是在渲染完成后异步执行,而componentDidMount是在渲染完成后同步执行,所以他们是不同的。
与componentDidMount更类似的是useLayoutEffect,他会在渲染完成后同步执行。
这里提供个在线Demo[1],你可以将Demo中的useLayoutEffect替换为useEffect,看看他们的区别。
通过本文,我们了解了useEffect的完整执行过程。
本系列文章接下来会继续以实例 + 源码的方式,解读业务中经常使用的React特性。
[1]
在线Demo: https://code.h5jun.com/haxufe/edit?js,output