内容简介:React 组件的生命周期,相信大家都非常熟悉了,无非那么几个函数,官方文档已经写得非常清楚了。(那还有什么好说的?浪费感情!合上!)一般我们所讨论的,都是单个组件的生命周期。如果是多个组件之间呢?比如父子组件?兄弟组件?各个周期又是什么样的?异步路由的情况呢?前阵子新出的 Hooks 呢?有几个人敢站出来说我全知道的?(反正我是不敢)刚好也是最近遇到一些关于生命周期的问题,项目中涉及到大量的异步操作,需要清楚地知道各部分的执行顺序,借此机会整理一下。
React 组件的生命周期,相信大家都非常熟悉了,无非那么几个函数,官方文档已经写得非常清楚了。(那还有什么好说的?浪费感情!合上!)
一般我们所讨论的,都是单个组件的生命周期。如果是多个组件之间呢?比如父子组件?兄弟组件?各个周期又是什么样的?异步路由的情况呢?前阵子新出的 Hooks 呢?有几个人敢站出来说我全知道的?(反正我是不敢)
刚好也是最近遇到一些关于生命周期的问题,项目中涉及到大量的异步操作,需要清楚地知道各部分的执行顺序,借此机会整理一下。
1)在你继续之前
这篇文章并不是入门教学,如果你对 React 一点不了解的话,或许这篇文章并不适合你。
我假定你已经掌握 React 的基本知识,例如:组件的生命周期、Hooks 的基本概念、类组件和函数组件的区别 等,并用 React 开发过有一定复杂度的应用。
这里我们不讨论 shouldComponentUpdate()
、 React.memo()
等优化手段,只考虑最原始的情况。
本文以浏览器作为目标环境,React Native 和 Electron 在基本概念上是一样的,细节上的不同不作为本文的讨论重点,
2)关于 Hooks 的生命周期
确切地说,Hooks 并不是一种新的组件类型,它只是一种代码复用的方式,并且总是伴随着函数组件一起出现。
在 Hooks 之前,函数组件是没有 state 的概念的,因而也就不存在生命周期一说,就只是一个 render 函数。Hooks 的出现,让函数组件也可以拥有 state,相应的也就引入了生命周期的概念,具体来说也就是 useEffect()
和 useLayoutEffect()
具体何时执行的问题。
函数组件的本质是函数,而函数本身是没有生命周期的,Hooks 的出现也没有改变这一点。这里我们讨论的对象是「组件」,组件是可以有生命周期的。因此当我在后面的文字中提到 Hooks 时,我其实是在表示「使用了 Hooks 的函数组件」(虽然这个说法不是很严谨,但是这不重要,你懂我意思就好)。
3)那么我们就来做个实验吧
为了一探究竟,我写了一个Demo 来模拟一些常见的用例:父子组件、兄弟组件、同步/异步路由、类组件和 Hooks、组件初始化时的异步操作(如访问 API)等。
如果你有遇到 Demo 没覆盖到的使用场景,欢迎提 Issue。
3.1)TL,DR;
我知道大家的时间都很宝贵,赶时间的朋友可以直接看结论;时间宽裕的朋友,我们从下一节开始细聊:
render render useEffect
3.2)挂载过程
父子组件的挂载分为三个阶段。
第一阶段,父组件执行到自身的 render
,解析其下有哪些子组件需要渲染,并对其中同步的子组件进行创建,挨个执行各组件到 render
,生成到目前为止的 Virtual DOM 树,并 commit 到 DOM。
第二阶段,此时 DOM 节点已经生成完毕,组件挂载完成,开始后续流程。先依次触发同步子组件各自的 componentDidMount
/ useLayoutEffect
,最后触发父组件的。
第三阶段,如果组件使用了 useEffect
,则会在第二阶段之后触发 useEffect
。如果父子组件都使用了 useEffect
,那么子组件先触发,然后是父组件。
如果父组件中包含异步子组件,则会在父组件挂载完成后被创建。
对于兄弟组件,如果是同步路由,它们的创建顺序和在父组件中定义的出场顺序是一致的。
对于「异步的兄弟组件」,最终的加载顺序是按照 JSX 中定义的顺序,还是按照 js 文件下载完成的顺序,我暂时还不能确定。
按照我对“异步”的理解,我更倾向于认为是按照下载完成的顺序,这更符合“按需加载”的概念。
之所以会造成困扰,是因为据我目前所观察到的情况,两种顺序是一致的,我还没有遇到过后定义但先加载的情况。
大部分时候我们会以页面为单位去划分异步组件,单个页面需要加载多个异步组件的场景比较少;即便在这些少数场景中,单次需要请求的文件数量也不会很多,不至于超过浏览器的并发上限;即便超过,也会按照在父组件中定义的出场顺序去分批发起请求。考虑到单个异步组件的文件尺寸通常都很小,加载速度非常快,同一批发起的请求基本上也都是同时到达,因此大部分时候下载完成的顺序和定义的顺序是一致的。
但没遇到不代表不存在,该问题我会进一步验证,已经有结果的小伙伴也可以分享一下。
如果组件的初始化过程包含异步操作(通常在 componentDidMount()
和 useEffect(fn, [])
中进行),这些操作何时得到响应与组件的生命周期无关,完全看异步操作本身花了多少时间。
3.3)更新过程
React 的设计遵循单向数据流模型,兄弟节点之间的通信也会经过父组件(Redux 和 Context 也是通过改变父组件传递下来的 props
实现的),因此任何两个组件之间的通信,本质上都可以归结为父组件更新导致子组件更新的情况。
父子组件的更新同样分为三个阶段。
第一、三阶段,和挂载过程基本一样,无非是第一阶段多了一个 Reconciliation 的过程,第三阶段需要先执行 useEffect
的 Cleanup 函数。
第二阶段,和挂载过程也很类似,都是子组件先于父组件,但更新比挂载涉及的函数要多一些:
-
getSnapshotBeforeUpdate()
-
useLayoutEffect() 的 Cleanup
-
useLayoutEffect()
/componentDidUpdate()
React 会按照上面的顺序依次执行这些函数,每个函数都是各个子组件的先执行,然后才是父组件的执行。具体说来,就是先执行各个子组件的 getSnapshotBeforeUpdate()
,然后是父组件的 getSnapshotBeforeUpdate()
,再然后是各个子组件的 componentDidUpdate()
,父组件的 componentDidUpdate()
,以此类推。
这里我们把类组件和 Hooks 的生命周期函数放在了一起,因为父子组件可以是这两种组件类型的任意排列组合。实际渲染时不一定每一个函数都有用到,只会调用组件实际拥有的函数。
3.4)卸载过程
卸载过程涉及到 componentWillUnmount()
、 useEffect()
的 Cleanup、 useLayoutEffect()
的 Cleanup 这三种函数,顺序固定为父组件的先执行,子组件按照在 JSX 中定义的顺序依次执行各自的方法。
注意,此时的 Cleanup 函数会按照在代码中定义的顺序先后执行,与函数本身的特性无关。
如果卸载旧组件的同时伴随有新组件的创建,新组件会先被创建并执行完 render
,然后卸载不需要的旧组件,最后新组件执行挂载完成的回调。
4)Hooks 的特别之处
根据 React 的官方文档, useEffect()
和 useLayoutEffect()
都是等效于 componentDidUpdate()
/ componentDidMount()
的存在,但实际上两者在一些细节上还是有所不同:
4.1)先来未必先走
useLayoutEffect()
永远比 useEffect()
先执行,即便在你的代码中 useEffect()
是写在前面的。所以 useLayoutEffect()
才是事实上和 componentDidUpdate()
/ componentDidMount()
平起平坐的存在。
useEffect()
会在父子组件的 componentDidUpdate()
/ componentDidMount()
都触发之后才被触发。当父子组件都用到 useEffect()
时,子组件中的会比父组件中的先触发。
4.2)不团结的 Cleanup
同样都拥有 Cleanup 函数, useLayoutEffect()
和它的 Cleanup 未必是挨着的。
当父组件是 Hooks、子组件是 Class 时,能够很明显看出, useLayoutEffect()
的 Cleanup 会在 getSnapshotBeforeUpdate()
和 componentDidUpdate()
之间被调用,而 useLayoutEffect()
则是和 componentDidUpdate()
同级,按照更新过程的顺序被调用。
Hooks 作为子组件时也是这么个过程,只是没有了子组件,看上去不那么明显罢了。
而 useEffect()
就不一样,它和它的 Cleanup 紧密团结在一起,每次执行都是前后脚一起的,从不分离。
以上所述就是小编给大家介绍的《探索 React 组件之间的生命周期》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
Spring揭秘
王福强 / 人民邮电出版社 / 2009.8 / 99.00元
没有教程似的训导,更多的是说故事般的娓娓道来,本书是作者在多年的工作中积累的第一手Spring框架使用经验的总结,深入剖析了Spring框架各个模块的功能、出现的背景、设计理念和设计原理,揭开了Spring框架的神秘面纱,使你“知其然,更知其所以然”。每部分的扩展篇帮助读者活学活用Spring框架的方方面面,同时可以触类旁通,衍生出新的思路和解决方案。 本书内容全面,论述深刻入理,必将成为每......一起来看看 《Spring揭秘》 这本书的介绍吧!