2-4-组件生命周期
React是如何渲染组件的
渲染组件时会触发的生命周期方法
挂载流程
更新流程
卸载流程
React是如何渲染组件的
我们就按照平时书写React代码的顺序来理清React把组件代码渲染到最终的真实DOM中的流程。
一般来讲,我们都会先定义一个组件。我们想要在页面中看到这个组件的渲染结果的化,就需要以JSX的形式将组件传入ReactDOM.render方法的第一个参数,我们要理解,这里的JSX经过React内部的转译,其实是一个React.createElement创建React元素的方法。render方法获取到React元素之后会将它实例化,之后它会根据实例化的React元素创建出真实的DOM元素,再根据第二个参数的指向,将创建好的元素插入到目标DOM容器当中。
为了加深理解,还是稍微来了解一下React本身运行流程。
在新版本的React当中,React的底层被重写了。React换上了一个新的引擎,这个引擎叫做React Fiber.React Fiber 作用的也即是React最核心的功能,它将React应用界面更新的过程分为了两个主要的部分:
调度过程
执行过程
前面还是一样,React对虚拟DOM进行Diff操作,对比应用前后改变的部分,之后进入调度阶段,React Fiber会计算出最优化的调度方法,以防止我们更新DOM是发生卡顿阻塞等状况,然后进入执行过程提交调度计算的结果,最终由ReactDOM负责将页面中的内容渲染到页面当中。
在调度过程中,有4个生命周期函数会被触发:
componentWillMount
componentWillReceiveProps
shouldComponentUpdate
componentWillUpdate
在执行过程中,有3个生命周期函数会被触发:
componentDidMount
componentDidUpdate
componentWillUnmount
React组件生命周期方法
React为了方便我们更好地控制自己的应用,提供了许多预置的生命周期方法。就好像我们上面介绍的挂载流程,这些固定的生命周期方法分别会在组件的挂载流程、更新流程、卸载流程中触发。
假如有同学实在是不理解上一个部分介绍的渲染原理也没有关系,在学习下面的内容之前,我们只需要明确,React的初次render会进行挂载流程,挂载之后的后续渲染进行的都是更新流程,最后,我们也可以通过ReactDOM.unmountComponentAtNode方法将组件卸载,这其中进行的就是卸载流程。
如果只是一直讲解概念,感觉会很抽象不好理解,所以我们不如来亲眼看一看这三个流程是如何运行,如何触发生命周期函数的:
这里我们定义了两个组件,父组件会传递state到子组件当中,顺便state的变化和props的传递流程我们也可以在这个示例中看到。子组件的每个生命周期函数中我们都在控制台输出了相关的信息,这样在它被触发的时候,我们在控制台中就能够观察到。
挂载流程
首先是组件初次渲染的挂载流程,我们通过一个按钮绑定渲染函数来手动触发组件的渲染。
初次挂载组件时,我们定义的组件类首先会被构造声明。在渲染之前会触发componentWillMount,渲染完成会触发componentDidMount这两个生命周期函数。
componentWillMount方法会在render之前被触发,只会在组件被渲染出来之前触发一次,如果你是使用ES6的Class声明组件的话,时机和作用几乎与constructor相同,在componentWillMount方法里对state定义或改变不会触发重新渲染。
componentDidMount在初次渲染完成之后被触发,也只会触发一次,在这个方法里你已经可以访问渲染出的DOM元素了。官方推荐在这个函数中进行一些例如ajax请求的操作,所以它也是我们最经常使用的生命周期函数。
更新流程
我们是在子组件中观察生命周期函数运行的,之前已经提到过了,这个示例中父组件会通过props向子组件传递state中的计数值,所以我们首先观察到的被触发的生命周期函数叫做componentWillReceiveProps这是在我们调用this.setState()方法之后,子组件收到新的props之后会触发的函数。值得提醒的一点是,正如这个函数命名中的Will,更新流程运行到这一步,组件的props还没有改变,我们可以通过nextProps获取到新的props,但如果我们试着查看this.props会发现它还是之前的值。
接下来被触发的shouldComponentUpdate是一个很特殊的函数,它默认会返回ture,但假如这个函数返回false的话,更新流程就会被跳过,render也不会继续被触发。假如你想要提升你应用的性能,可以在这个函数中自定义一些判断来跳过不需要被更新的组件。
然后通过实验我们也可以看到,组件在初次渲染流程或者使用forceUpdate方法时是不会触发这个函数的。
接下来的两个生命周期函数componentWillUpdate和componentDidUpdate分别会在render的前后被触发。
需要注意的是在componentWillUpdate无法调用this.setState()你可以理解为更新流程到这一步想要再更改state已经晚了。如果有需要你可以在之前的componentWillReceiveProps中更新state,React会把改变合并到一个更新流程里进行。
而componentDidUpdate则是另一个比较适合我们发起ajax请求的地方,在这个方法里我们还可以比较前后的props变化,再决定是否发起网络请求。一个比较实际的使用场景是保存用户输入到服务器,用户可能会来来回回修改输入的内容,但假如我们判断在修改前后数据最终没有改变,就没有必要发起不必要的网络请求了。
卸载流程
组件既然可以被挂载,同样也可以被卸载,我们可以通过一个名为ReactDOM.unmountComponentAtNode的方法来卸载已经挂载的React组件。在卸载流程中,名为componentWillUnmount的函数会被触发。在组件挂载之后,我们可能定义了一些计时器、绑定了事件监听函数等等,在卸载流程的生命周期函数中,也是我们解绑这些函数的合适位置。
实例
接下来呢,我们一起来实现一个小的例子,掌握一下生命周期函数的具体使用方法。
我们要写的是一个从Reddit异步获取帖子内容的小应用。
我们还是沿用之前的展示组件与容器组件的划分方法,一共要写两个组件。
首先我们来写应用的展示组件。
我们通过 const 关键字声明,使用箭头函数定义一个简单的名为RedditList的组件。
一般我们的组件包含多层嵌套的JSX的时候,我们都会习惯在最外面套上一层小括号,因为浏览器在某些情况下可能会自动给换行的代码加上分号,在最外面套上小括号可以消除这一影响。
首先我们定义组件的标题,也就是帖子板块的名称,在这里我们使用字符串模板的方法,就可以省去一些运算符拼接字符串。
之后我们要使用JSX来渲染一个列表。在之前的课程中我们介绍过,JSX当中是可以使用JS的表达式的。一般在渲染列表的时候,我们会拿到一个数组数据,之后通过数组的map方法来渲染出列表的每一项。在这里我们就直接使用JS当中数组的map方法,在传入的函数当中,我们还是使用箭头函数的语法,数组中的每一项对应也返回列表中的一项。
需要特别强调的是,JSX当中渲染列表项的时候,每一项必须带有key属性作为识别列表中某一项的标识。React在渲染组件时需要通过key属性的值来判定列表中的每一项内容。
接下来我们来编写容器组件 RedditFetch . 我们通过类定义的方法来声明它。在它的构造函数里,我们先初始化这个应用的state,在刚才的展示组件当中,我们使用的是一个数组数据,因此在初始化的时候,我们也要把对应的状态数据初始化为数组。
接下来我们定义一个从服务器异步获取数据的方法fetchFromApi,在这里我们使用的是一个名为axios的专门用来发起异步请求的库,当然你也可以用jQuery或者原生的JS方法,你可以选择你自己熟悉的实现方式。在请求成功之后呢,重新设置我们应用的state,获取到posts帖子的数据。
之后就是最关键的部分啦。在这里我们要使用到两个生命周期函数,第一个是componentDidMount,我们刚才介绍过,这里是最适合发起异步服务器请求的地方。因此在其中我们调用刚才定义好的fetchFromApi方法。
再然后是componentWillUnMount,这个生命周期函数是在组件的卸载过程中被触发的,一般在这个生命周期函数中,我们都是做一些解除取消的方法,比方说在这里,我们可以取消掉向服务器发起的请求。
最后在组件的render方法中,调用刚才定义的展示组件RedditList,传入对应的props值,板块名称以及帖子数据,就都OK啦。
最后在ReactDOM的渲染方法中,我们为整个应用传入板块的名称,效果就是我们现在看到的。
总结
React组件渲染包含三个流程:挂载流程、更新流程、卸载流程
各个生命周期函数会在特定的时刻触发并适用于不同的使用场景
通过使用生命周期函数我们可以对应用进行更精准的控制
如果你需要发起网络请求,将其安排在合适的生命周期函数中是值得推荐的做法
了解掌握React组件渲染的流程和原理对我们更深入掌握React非常有帮助
Last updated