做为React
开发者,你能答上以下两个问题么:web
-
对于以下函数组件:
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
工做流程
本文接下来将深刻源码,带你了解这些知识。app
这,就是关于useEffect
的一切。异步
useEffect的执行顺序
React
的源码能够拆分为三块:编辑器
-
调度器:调度更新 -
协调器:决定更新的内容 -
渲染器:将更新的内容渲染到视图中
其中,只有渲染器
会执行渲染视图操做。函数
对于浏览器环境来讲,只有渲染器
会执行相似appendChild
、insertBefore
这样的DOM
操做。flex
协调器
如何决定更新的内容呢?url
答案是:他会为须要更新的内容对应的fiber
(能够理解为虚拟DOM
)打上标记。
这些被打标记的fiber
会造成一条链表effectList
。
渲染器
会遍历effectList
,执行标记对应的操做。
-
好比
Placement
标记对应插入DOM
-
好比
Update
标记对应更新DOM
属性
useEffect
也遵循一样的工做原理:
-
触发更新时,
FunctionComponent
被执行,执行到useEffect
时会判断他的第二个参数deps
是否有变化。 -
若是
deps
变化,则useEffect
对应FunctionComponent
的fiber
会被打上Passive
(即:须要执行useEffect)的标记。 -
在
渲染器
中,遍历effectList
过程当中遍历到该fiber
时,发现Passive
标记,则依次执行该useEffect
的destroy
(即useEffect
回调函数的返回值函数)与create
(即useEffect
回调函数)。
其中,前两步发生在协调器
中。
因此,effectList
构建的顺序就是useEffect
的执行顺序。
effectList
协调器
的工做流程是使用遍历
实现的递归
。因此能够分为递
与归
两个阶段。
咱们知道,递
是从根节点向下一直到叶子节点,归
是从叶子节点一路向上到根节点。
effectList
的构建发生在归
阶段。因此,effectList
的顺序也是从叶子节点一路向上。
useEffect
对应fiber
做为effectList
中的一个节点,他的调用逻辑也遵循归
的流程。
如今,咱们有充足的知识回答第一个问题:
因为归
阶段是从Child
到Parent
到App
,因此相应effectList
也是一样的顺序。
因此useEffect
回调函数执行也是一样的顺序。
不要用生命周期钩子类比hook
咱们在初学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
是在渲染完成后同步执行,因此他们是不一样的。
useEffect与useLayoutEffect
与componentDidMount
更相似的是useLayoutEffect
,他会在渲染完成后同步执行。
这里提供个在线Demo[1],你能够将Demo
中的useLayoutEffect
替换为useEffect
,看看他们的区别。
总结
经过本文,咱们了解了useEffect
的完整执行过程。
本系列文章接下来会继续以实例 + 源码的方式,解读业务中常常使用的React
特性。
点击阅读原文,开源电子书轻松学懂React源码
参考资料
在线Demo: https://code.h5jun.com/haxufe/edit?js,output
本文分享自微信公众号 - 牧码的星星(gh_0d71d9e8b1c3)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。