探索 React 组件之间的生命周期

栏目: IOS · Android · 发布时间: 4年前

内容简介: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 函数。

第二阶段,和挂载过程也很类似,都是子组件先于父组件,但更新比挂载涉及的函数要多一些:

  1. getSnapshotBeforeUpdate()
  2. useLayoutEffect() 的 Cleanup
  3. 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 组件之间的生命周期》,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对 码农网 的支持!

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Agile Web Development with Rails, Third Edition

Agile Web Development with Rails, Third Edition

Sam Ruby、Dave Thomas、David Heinemeier Hansson / Pragmatic Bookshelf / 2009-03-17 / USD 43.95

Rails just keeps on changing. Rails 2, released in 2008, brings hundreds of improvements, including new support for RESTful applications, new generator options, and so on. And, as importantly, we’ve a......一起来看看 《Agile Web Development with Rails, Third Edition》 这本书的介绍吧!

JS 压缩/解压工具
JS 压缩/解压工具

在线压缩/解压 JS 代码

随机密码生成器
随机密码生成器

多种字符组合密码

XML 在线格式化
XML 在线格式化

在线 XML 格式化压缩工具