三大框架中实例化组件并渲染组件视图时,组件实例的生命周期就开始了。生命周期一直伴随着变更检测,框架会检查数据绑定属性何时发生变化,并按需更新视图和组件实例。当从应用中销毁组件实例并从 DOM 中移除它渲染的模板时,生命周期就结束了。
我们的应用可以使用生命周期钩子方法来触发组件生命周期中的关键事件,以初始化新实例,需要时启动变更检测,在变更检测过程中响应更新,并在删除实例之前进行清理。
三大框架关于组件生命周期钩子各不相同,但是对于我们来说最常用到的是挂载、更新和卸载这几个钩子,本章将对这三个钩子来做比较。
Angular 组件生命周期钩子
在这里,我们先列出 Angular 组件生命周期中的钩子,下表中的关于钩子的次序就是组件在生命周期中的执行顺序。
钩子方法 | 用途 | 时机 |
---|---|---|
ngOnChanges | 当 Angular 设置或重新设置数据绑定的输入属性时响应。该方法接受当前和上一属性值的对象。 | 如果组件绑定过输入属性,那么在 ngOnInit() 之前以及所绑定的一个或多个输入属性的值发生变化时都会调用。 |
ngOnInit | 在 Angular 第一次显示数据绑定和设置组件的输入属性之后,初始化组件。 | 在第一轮 ngOnChanges() 完成之后调用,只调用一次。而且即使没有调用过 ngOnChanges(),也仍然会调用 ngOnInit()(比如当模板中没有绑定任何输入属性时)。 |
ngDoCheck | 检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应。 | 紧跟在每次执行变更检测时的 ngOnChanges() 和 首次执行变更检测时的 ngOnInit() 后调用。 |
ngAfterContentInit | 当 Angular 把外部内容投影进组件视图或指令所在的视图之后调用。 | 第一次 ngDoCheck() 之后调用,只调用一次。 |
ngAfterContentChecked | 每当 Angular 检查完被投影到组件中的内容之后调用。 | ngAfterContentInit() 和每次 ngDoCheck() 之后调用。 |
ngAfterViewInit | 当 Angular 初始化完组件视图及其子视图调用。 | 第一次 ngAfterContentChecked() 之后调用,只调用一次。 |
ngAfterViewChecked | 每当 Angular 做完组件视图和子视图或包含该指令的视图的变更检测之后调用。 | ngAfterViewInit() 和每次 ngAfterContentChecked() 之后调用。 |
ngOnDestroy | 每当 Angular 每次销毁组件后调用并清扫。在这儿取消订阅可观察对象和事件绑定,以防内存泄漏。 | 在 Angular 销毁指令或组件后立即调用。 |
在 Angular 的组件生命周期中并没有称之为挂载、更新和卸载的钩子,为了更方便的在三个框架中做对比,我们把它生命周期中类似于这几个功能的钩子称之为挂载、更新和卸载。
挂载钩子
ngAfterViewInit 钩子会在完全初始化了组件的视图后调用,它只会调用一次。这个钩子中没有参数。
更新钩子
ngOnChanges 钩子会在组件的输入属性发生了变化时调用,它会在组件初始化之前先调用一次。ngOnChanges() 方法获取了一个对象,它把每个发生变化的属性名都映射到了一个 SimpleChange 对象,该对象中有属性的当前值和前一个值。这个钩子会在这些发生了变化的属性上进行迭代,并记录它们。
如果组件并没有绑定任何输入属性,那么此钩子不会被调用。
需要注意的是在 Angular 组件的生命周期中并没有组件自身属性变化后可调用的钩子,如果要监测组件自身属性的变化,可以使用 RxJS 库来创建和订阅可观察的对象。
卸载钩子
ngOnDestroy 钩子会在组件实例被销毁后调用,这个钩子中没有参数。
React 组件生命周期钩子
和其他两个框架不一样,在 React 的函数式组件中并没有可调用的生命周期钩子。想要实现类似挂载、更新和卸载功能的钩子可以使用 useEffect 钩子。
需要注意的是,在编写和读取 Effect 时,要独立地考虑每个 Effect(如何开始和停止同步),而不是从组件的角度思考(如何挂载、更新或卸载)。在本章中是为了与其他两个框架做比较,才从组件的角度来使用 Effect 的。
在这里我们提供一段使用 useEffct 来实现上述三个钩了的示例。
import { useState, useEffect } from 'react';
export default function Clock({ time }) {
const [index, setIndex] = useState(0);
useEffect(() => {
// 【更新钩子】当props与state变更、组件渲染后执行
});
useEffect(() => {
// 【挂载钩子】组件渲染后仅执行一次
return () => {
// 【卸载钩子】组件在卸载后执行
}
}, []);
useEffect(() => {
// 【更新钩子】仅在index变更,组件渲染后执行
}, [index]);
function handleClick() {
setIndex(index + 1);
}
return (
<>
<button onClick={handleClick}>
Next
</button>
<p>{index}</p>
<div>{time}</div>
<>
);
}
挂载钩子
使用 useEffect 钩子,第二个参数传递 [] 时,会在组件渲染后仅调用一次这个钩子。
useEffect(() => {
// 【挂载钩子】组件渲染后仅执行一次
}, []);
更新钩子
使用 useEffect 钩子,不传递第二个参数时,会在 props 或 state 发生变更、组件渲染后调用这个钩子。
useEffect(() => {
// 【更新钩子】当props与state变更、组件渲染后执行
});
卸载钩子
在 useEffect 钩子中添加返回的函数会在组件被卸载后执行。
useEffect(() => {
// 【卸载钩子】组件在卸载后执行
});
Vue组件生命周期钩子
在这里,我们先列出 Vue 组件生命周期中的钩子,下表中的关于钩子的次序就是组件在生命周期中的执行顺序。这些钩子都应该在组件的 setup() 阶段被同步调用。
钩子方法 | 用途 | 时机 |
---|---|---|
onBeforeMount | 当这个钩子被调用时,组件已经完成了其响应式状态的设置,但还没有创建 DOM 节点。它即将首次执行 DOM 渲染过程。 | 在组件被挂载之前被调用。 |
onMounted | 这个钩子通常用于执行需要访问组件所渲染的 DOM 树相关的副作用。 | 在组件挂载完成后执行。 |
onBeforeUpdate | 这个钩子可以用来在 Vue 更新 DOM 之前访问 DOM 状态。在这个钩子中更改状态也是安全的。 | 在组件即将因为响应式状态变更而更新其 DOM 树之前调用。 |
onUpdated | 这个钩子会在组件的任意 DOM 更新后被调用,这些更新可能是由不同的状态变更导致的,因为多个状态变更可以在同一个渲染周期中批量执行(考虑到性能因素)。 | 在组件因为响应式状态变更而更新其 DOM 树之后调用。 |
onBeforeUnmount | 当这个钩子被调用时,组件实例依然还保有全部的功能。 | 在组件实例被卸载之前调用。 |
onUnmounted | 可以在这个钩子中手动清理一些副作用,例如计时器、DOM 事件监听器或者与服务器的连接。 | 在组件实例被卸载之后调用。 |
挂载钩子
onMounted 钩子在组件挂载完成后调用,它只会执行一次。
更新钩子
onUpdated 钩子在组件因为响应式状态变更而更新其 DOM 树之后调用。
卸载钩子
onUnmounted 钩子在组件实例被卸载之后调用。
小结
本章介绍了三大框架组件的生命周期中的钩子,对挂载、更新与卸载钩子做了重点说明。
Angular 中是类组件,实例化的组件可以理解成一个类的对象,把组件实例生命周期的钩子当成对象的方法考虑会更好理解。
React 函数组件中并没有可以调用的生命周期钩子,在编写和读取 Effect 时,要独立地考虑每个 Effect(如何开始和停止同步),而不是从组件的角度思考(如何挂载、更新或卸载)。
Vue 组件生命周期中关于挂载、更新和卸载的钩子符合我们的直观描述,更好理解一些。
文章参考链接:
- https://angular.cn/guide/lifecycle-hooks
- https://angular.cn/api/core/OnChanges
- https://angular.cn/api/core/AfterViewInit
- https://zh-hans.react.dev/reference/react/useEffect
- https://zh-hans.react.dev/learn/synchronizing-with-effects
- https://zh-hans.react.dev/learn/lifecycle-of-reactive-effects
- https://zh-hans.react.dev/reference/react/useEffect
- https://cn.vuejs.org/guide/essentials/lifecycle.html
- https://cn.vuejs.org/api/composition-api-lifecycle.html