React与Vue组件生命周期以及钩子函数
组件生命周期
组件生命周期:组件从被创建到挂载到页面中运行,再到组件不用时卸载的过程。
-
钩子函数,使开发者的代码可以在固定阶段执行。
-
注意:只有类组件才有生命周期。函数组件每次都是重新运行函数,旧的组件即刻被销毁。
React
组件的生命周期
组件的生命周期主要包括三阶段
- 挂载阶段
- 更新阶段
- 卸载阶段
-
挂载阶段:组件被创建,执行初始化,并被挂载到DOM中,完成组件的第一次渲染(已经插入真实DOM)
-
更新阶段:组件被挂载到DOM之后,
props
和state
都可以引起组件的更新(重新渲染) -
卸载阶段:在组件被卸载前调用(已经移出真实DOM)
挂载阶段
-
constructor()
-
ES6
中class
的构造方法,组件被创建时会首先调用组件的构造方法 -
初始化
state
,以及为事件处理程序绑定this
-
-
componentWillMount() 不建议使用,已经废用了,是在组件被挂载在DOM之前调用,而且只会被调用一次
-
render()
-
根据组件的
props
和state
返回一个React
元素 -
渲染
UI
-
注意:在
render()
里面不能调用setState()
-
实际上是调用
React.creatElement()
-
-
componentDidMount()
-
在组件被挂载在DOM之后调用,而且只会被调用一次。
-
可以调用 setState()
-
向服务器端发送网络请求,以及处理依赖DOM节点的操作
-
更新阶段
- 父组件调用
render
方法- 组件调用
this.setState
-
componentWillReceiveProps()
- 已废用
- 由
props
引起的组件更新过程中被调用 - 由
state
引起的组件更新并不会触发该方法的执行
-
shouldComponentUpdate()
-
用于决定组件是否继续执行更新过程,默认值为
true
-
可以用来减少组件不必要的渲染,从而优化组件的性能
-
对比前后两个
props
和state
是否相同
-
-
componentWillUpdate()
-
组件初始化时不调用
-
只有在组件将要更新时才调用,此时可以修改
state
-
-
render()
-
根据组件的
props
和state
返回一个React
元素 -
渲染
UI
-
注意:在
render()
里面不能调用setState()
-
实际上是调用
React.creatElement()
-
-
componentDidUpdate()
- 组件更新之后被调用
卸载阶段
-
componentWillUnmount()
- 在组件被卸载前调用
其他
-
componentDidCatch(error, errorInfo)在子组件发生错误是被调用。
- 接收
error
和errorInfo
两个参数,error
表示抛出的错误errorInfo
带有componentStack key
的对象,其中包含有关组件引发错误的栈信息
- 接收
React 生命周期函数调用顺序
简记,容易记住的生命周期方法
单个组件生命周期函数调用顺序
-
单组件初始化
-
constructor
->componentWillMount
->render
->componentDidMount
-
常用
constructor
->render
->componentDidMount
-
-
单组件更新
state
shouldComponentUpdate
->componentWillUpdate
->render
->componentDidUpdate
-
单组件卸载
componentWillUnmount
父子组件生命周期函数调用顺序
-
父子组件初始化
父组件constructor
->父组件componentWillMount
->父组件render
->子组件constructor
->子组件componentWillMount
->子组件render
->子组件componentDidMount
->父组件componentDidMount
-
父组件更新
state
父组件shouldComponentUpdate
->父组件componentWillUpdate
->父组件render
->子组件componentWillReceiveProps
->子组件shouldComponentUpdate
->子组件componentWillUpdate
->子组件render
->子组件componentDidUpdate
->父组件componentDidUpdate
-
子组件更新
state
子组件shouldComponentUpdate
->子组件componentWillUpdate
->子组件render
->子组件componentDidUpdate
-
父子组件卸载
父组件componentWillUnmount
->子组件componentWillUnmount
React
组件的生命周期(新版)
新版本主要是React 16.3+
新版本的
react
生命周期函数主要变化
- 去除了三个不安全函数
componentWillMount
、componentWillReceiveProps
、componentWillUpdate
- 新增了三个生命周期函数
static getDerivedStateFromProps
、getSnapshotBeforeUpdate
、static getDerivedStateFromError
-
版本说明
-
componentWillMount
,componentWillReceiveProps
,componentWillUpdate
表示使用这些生命周期的代码,有可能在未来的 React 版本中存在缺陷,可能会影响未来的异步渲染。 -
React 16.3
版本:为不安全的生命周期引入别名 UNSAFE_componentWillMount,UNSAFE_componentWillReceiveProps 和 UNSAFE_componentWillUpdate。(旧的生命周期名称和新的别名都可以在此版本中使用) -
React 16.3
之后的版本:为 componentWillMount,componentWillReceiveProps 和 componentWillUpdate 启用弃用警告。(旧的生命周期名称和新的别名都可以在此版本中使用,但旧名称会记录DEV模式警告) -
React 17.0
版本:推出新的渲染方式—异步渲染( Async Rendering),提出一种可被打断的生命周期(实际 dom 挂载之前的虚拟 dom 构建阶段),去掉的三个生命周期 componentWillMount,componentWillReceiveProps 和 componentWillUpdate。(从这个版本开始,只有新的“UNSAFE_”生命周期名称将起作用)
-
挂载阶段
constructor -> getDerivedStateFromProps -> render -> componentDidMount
- componentWillMount 不再使用
- getDerivedStateFromProps 替换 componentWillMount
-
static getDerivedStateFromProps(props, state) 在组件初始化和组件更新时都会被调用
-
接收
state
和props
两个参数- 可以通过返回一个对象来更新组件自身的
state
- 返回
null
来表示接收到的props
没有变化,不需要更新 state
- 可以通过返回一个对象来更新组件自身的
-
静态方法,所以该生命周期钩子函数内部没有
this
-
将父组件传递过来的
props
映射到子组件的state
上 -
注意:子组件通过 setState 更新自身状态时,不会改变父组件的 props
-
更新阶段
getDerivedStateFromProps -> shouldComponentUpdate -> render -> getSnapshotBeforeUpdate -> componentDidUpdate
- 新增 getDerivedStateFromProps
- 替换 componentWillUpdate 为 getSnapshotBeforeUpdate
-
getSnapshotBeforeUpdate(prevProps, prevState) 在组件更新时被调用。被调用于 render之后、更新 DOM 和 refs 之前。
-
必须有返回值,返回值将作为 componentDidUpdate 函数方法的第三个参数。
-
通过
this.props
和this.state
是最新的数据,函数的参数prevProps
和prevState
是更新前的数据。可以通过比较,做逻辑处理。
-
卸载阶段
componentWillUnmount
无变化
其他
-
static getDerivedStateFromError(error) 在后代组件抛出错误后被调用。 它将抛出的错误作为参数,并返回一个值以更新 state。
-
在渲染DOM之前调用,当我们遇到子组件出错的时候可以渲染备用UI,常用作错误边界。
-
componentDidCatch
是在DOM渲染完后才会调用,可以用来输出错误信息或上传一些错误报告。
-
React
生命周期函数调用顺序
单组件
-
单组件初始化
constructor
->getDerivedStateFromProps
->render
->componentDidMount
-
单组件更新
state
getDerivedStateFromProps
->shouldComponentUpdate
->render
->getSnapshotBeforeUpdate
->componentDidUpdate
-
单组件卸载
componentWillUnmount
父子组件
-
父子组件初始化
父组件constructor
->父组件getDerivedStateFromProps
->父组件render
->子组件constructor
->子组件getDerivedStateFromProps
->子组件render
->子组件componentDidMount
->父组件componentDidMount
-
父组件更新state
父组件getDerivedStateFromProps
->父组件shouldComponentUpdate
->父组件render
->子组件getDerivedStateFromProps
->子组件shouldComponentUpdate
->子组件render
->子组件getSnapshotBeforeUpdate
->父组件getSnapshotBeforeUpdate
-> 子组件componentDidUpdate -> 父组件componentDidUpdate
-
子组件更新state
子组件getDerivedStateFromProps
->子组件shouldComponentUpdate
->子组件render
->子组件getSnapshotBeforeUpdate
->子组件componentDidUpdate
-
父子组件卸载
父组件componentWillUnmount
->子组件componentWillUnmount
VUE@2
组件的生命周期
组件的生命周期
- 初始化阶段
- 编译阶段
- 挂载阶段
- 更新阶段
- 销毁阶段
初始化阶段
-
beforeCreate
-
在实例初始化之后,进行数据侦听和事件/侦听器的配置之前同步调用。
-
实例的
data
和methods
等配置还未初始化,无法调用,只能使用一些默认事件。
-
-
created
-
在实例创建完成后被立即同步调用。
-
实例已完成对选项的处理,意味着以下内容已被配置完毕:数据侦听、计算属性、方法、事件/侦听器的回调函数。
-
挂载阶段还没开始,且 $el.property 目前尚不可用。
-
模板还没有编译,不能获取到DOM。
-
编译阶段
-
VUE对象是否指定
el
-
VUE对象是否指定
template
-
将
<template>
编译到render
函数中
挂载阶段
-
beforeMounted
-
挂载开始之前被调用,相关的 render 函数首次被调用。
-
函数在模板渲染之前调用,也就是DOM节点挂载到真实DOM树之前调用。
-
此模板进行编译,会调用
render
函数生成虚拟DOM
,同样无法获取DOM节点(此时只存在虚拟DOM
,还在JS级别)。
-
-
mounted
-
实例被挂载后调用,这时 el 被新创建的 vm.$el 替换。
-
注意:
mounted
不会保证所有的子组件也都被挂载完成。- 如果你希望等到整个视图都渲染完毕再执行某些操作,可以在 mounted 内部使用 vm.$nextTick
-
执行该钩子函数时,模板编译完成,而且挂载到真实DOM树上面去了,也就是页面可以显示了。(该钩子在服务器端渲染期间不被调用)
-
生命周期组件调用顺序
单组件
-
单组件初始化
- beforeCreate -> created -> beforeMount -> mounted
-
单组件更新
- beforeUpdate -> updated
-
单组件卸载
- beforeDestroy -> destroyed
父子组件
-
父子组件初始化
- 父组件beforeCreate -> 父组件created -> 父组件beforeMount -> 子组件beforeCreate -> 子组件created -> 子组件beforeMount -> 子组件mounted -> 父组件mounted
-
父组件更新data,此data没传递给子组件
- 父组件beforeUpdate -> 父组件updated
-
父组件更新data,此data传递给了子组件
- 父组件beforeUpdate -> 子组件beforeUpdate -> 子组件updated -> 父组件updated
-
子组件更新data
- 子组件beforeUpdate -> 子组件updated
-
父子组件卸载
- 父组件beforeDestroy -> 子组件beforeDestroy -> 子组件destroyed -> 父组件destroyed
更新阶段
-
beforeUpdate
-
在数据发生改变后,DOM 被更新之前被调用。
-
这里适合在现有 DOM 将要被更新之前访问它,比如移除手动添加的事件监听器。
-
该钩子在服务器端渲染期间不被调用,因为只有初次渲染会在服务器端进行。
-
该钩子函数在data数据发生变化之后调用,
data
里面的数据已经是最新的了,但是页面上DOM还没有更新最新的数据。
-
-
updated
-
在数据更改导致的虚拟 DOM 重新渲染和更新完毕之后被调用。
-
当这个钩子被调用时,组件 DOM 已经更新,可以执行依赖于 DOM 的操作。要相应状态改变,通常最好使用计算属性或 watcher 取而代之。
-
注意,updated 不会保证所有的子组件也都被重新渲染完毕。如果你希望等到整个视图都渲染完毕,可以在 updated 里使用 vm.$nextTick
-
该钩子函数会在data数据更新之后执行,而且此时页面也渲染更新完成了,显示的就是最新的数据。
-
注意:不要在
updated
中修改data
数据,很容易造成死循环。
-
销毁阶段
-
beforeDestroy
-
实例销毁之前调用。实例仍然完全可用。
-
在这个钩子函数里面解除一些全局或者自定义事件。
-
-
destroyed
-
实例销毁后调用。
-
该钩子被调用后,对应 Vue 实例的所有指令都被解绑,所有的事件监听器被移除,所有的子实例也都被销毁。
-
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>VUE@2生命周期</title>
<meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,user-scalable=no" />
</head>
<body>
<div id="app">
{{ message }}
</div>
<div class="test">
{{ msg }}
</div>
<!-- 生产环境版本,优化了尺寸和速度 -->
<script src="https://cdn.jsdelivr.net/npm/vue@2"></script>
<script>
const app = new Vue({
el: '#app',
data: {
message: 'Hello Vue!'
}
});
</script>
<script type="text/javascript">
const myVue = new Vue({
el: ".test",
data: {
msg: "在控制台输入 myVue.msg=666666,可以改变值触发更新前,更新后"
},
beforeCreate: function () {
console.log("建立前 --- beforeCreate");
},
created: function () {
// 在实例创建之后同步调用。数据绑定,计算属性,方法,watcher/事件回调。
// 没有开始 DOM 编译,$el 还不存在,但是实例存在,即 this.a 存在,可打印出来 。
console.log("建立 --- created");
},
beforeMount: function () {
console.log("渲染前 --- beforeMount");
},
mounted: function () {
console.log("渲染后 --- mounted");
},
beforeUpdate: function () {
console.log("更新前 --- beforeUpdate");
},
updated: function () {
console.log("更新后 --- updated");
},
beforeDestroy: function () {
// 触发方式,在console里面打myVue.$destroy();
// 在开始销毁实例时调用。此时实例仍然有功能。
console.log("销毁前 --- beforeDestroy");
},
destroyed: function () {
// 触发方式,在console里面打myVue.$destroy(); 其中myVue.$destroy(true)是删除DOM节点,会触发detached函数,但是实例仍然存在
// 在实例被销毁之后调用。此时所有的绑定和实例的指令已经解绑,注意是解绑不是销毁,所有的子实例也已经被销毁。
console.log("已销毁 --- destroyed");
}
});
</script>
</body>
其他钩子函数
-
errorCaptured
-
在捕获一个来自后代组件的错误时被调用。
-
钩子函数的三个参数:错误对象、发生错误的组件实例、一个包含错误来源信息的字符串。
-
该钩子函数可以返回 false 以阻止该错误继续向上传播。
-
-
activated
- 被 keep-alive 缓存的组件激活时调用。
-
deactivated
- 被 keep-alive 缓存的组件失活时调用。
VUE@3
组件的生命周期
-
setup() : 开始创建组件之前,在 beforeCreate 和 created 之前执行,创建的是 data 和 method
-
onBeforeMount() : 组件挂载到节点上之前执行的函数;
-
onMounted(): 组件挂载完成后执行的函数;
-
onBeforeUpdate(): 组件更新之前执行的函数;
-
onUpdated(): 组件更新完成之后执行的函数;
-
onBeforeUnmount(): 组件卸载之前执行的函数;
-
onUnmounted(): 组件卸载完成后执行的函数;
-
onActivated(): 被包含在
<keep-alive>
中的组件,会多出两个生命周期钩子函数,被激活时执行; -
onDeactivated(): 比如从 A 组件,切换到 B 组件,A 组件消失时执行;
-
onErrorCaptured(): 当捕获一个来自子孙组件的异常时激活钩子函数。
生命周期执行顺序
生命周期函数的运行基本顺序是
setup
->beforeCreate
->created
单个组件
-
单组件初始化
setup
->created
->onBeforeMount
->onRenderTracked
->onMounted
-
单组件更新
onRenderTriggered
->onBeforeUpdate
->onUpdated
-
单组件卸载
onBeforeDestroy
->onDestroyed
父子组件
-
父子组件初始化
- 父组件setup -> 父组件onBeforeMount -> 父组件onRenderTracked -> 子组件setup -> 子组件onBeforeMount -> 子组件onRenderTracked -> 子组件onMounted -> 父组件onMounted
-
父组件更新data,此data没传递给子组件
- 父组件onRenderTriggered-> 父组件onBeforeUpdate ->父组件 onUpdated
-
父组件更新data,此data传递给了子组件
- 父组件onRenderTriggered -> 父组件onBeforeUpdate -> 子组件onBeforeUpdate -> 子组件onUpdated -> 子组件onUpdated
-
子组件更新data
- 子组件onRenderTriggered -> 子组件onBeforeUpdate -> 子组件onUpdated
-
父子组件卸载
- 父组件onBeforeDestroy -> 子组件onBeforeDestroy -> 子组件onDestroyed -> 父组件onDestroyed
VUE@2
与VUE@3
对比
vue@2 | vue@3 |
---|---|
beforeCreate | setup(()=>{}) |
created | setup(()=>{}) |
beforeMount | onBeforeMount(()=>{}) |
mounted | onMounted(()=>{}) |
beforeUpdate | onBeforeUpdate(()=>{}) |
updated | onUpdated(()=>{}) |
beforeDestroy | onBeforeUnmount(()=>{}) |
destroyed | onUnmounted(()=>{}) |
activated | onActivated(()=>{}) |
deactivated | onDeactivated(()=>{}) |
errorCaptured | onErrorCaptured(()=>{}) |
Vue@2
和Vue@3
钩子变化不大,使用setup()
钩子来替代beforeCreate、created
两个钩子函数。
VUE
与React
生命周期对比
对比的目的在于处理相同与不同
相同点
-
Vue
、React
生命周期函数基本类似,组件的创建、更新、卸载都有对应的函数方法调用。 -
单组件、父子组件渲染类似。
不同点
-
参数比较:
Vue
生命周期函数除了 errorCaptured 都是没有参数的。React
生命周期函数都有参数并且能访问到旧的或新的props和state。
-
组件更新
React
父组件更新,子组件一定都会更新渲染,除非自己手动优化(继承PureComponent
或者实现shouldComponent
方法)。Vue
中除非子组件依赖父组件的数据改变了,否则子组件是不会重新渲染的。
-
组件更新处理
Vue
数据改变就一定会重新渲染,无法阻止。React
能通过shouldComponent
方法来决定是否需要渲染。
-
错误处理
Vue
在组件报错的时候页面还是会渲染,只是引发出错的地方可能数值不对。React
组件报错的时候会导致页面空白,编译报错将提示信息到浏览器(getDerivedStateFromError 做边界处理)。
-
生命周期影响
React
的生命周期函数,大多都在处理当前的props
与state
VUE
相比较多了缓存的生命周期函数 activated、deactivated 和用于开发调试的renderTracked、renderTriggered生命周期函数。