对于前端工程师来说 Angular 这个名字并不陌生。虽然现在是 Vue 大行其道的时代,但是最早在国内引入前端框架,MVC,数据双向绑定等等概念的就是这个老牌的框架——Angular。即便是在现在,在面对大型项目时,Angular 也是一个非常合适的框架。尤其是在近些年 Angular 也引入了组件化的概念。
那么说到组件化就不得不提生命周期的概念了。组件包含钩子函数,可以在实例化的不同时期实现对组件的监控和修改。当我第一次接触 Angular Component 的时候对于 OnInit 和 OnChanges 有一个直观的判断,即 OnInit 进行初始化操作 OnChanges 监听传入值并依据更新后的值修改组件状态。也就是说 OnInit 先执行,OnChanges 后执行。那么事实果真如我所想吗?
OnInit & OnChanges
OnInit
在默认更改检测器第一次检查指令的数据绑定属性之后,并且在检查任何视图或内容子项之前立即调用的回调方法。 当组件被实例化时,它只被调用一次。
所谓的第一次检查指令的数据绑定属性指的是 OnChanges 会先于 OnInit 被执行一次。
OnChanges
一种回调方法,在默认更改检测器已检查数据绑定属性(如果至少有一个已更改)之后,并且在检查视图和内容子项之前立即调用。
综上所述
从上面的描述可以看出 OnChanges 会先于 OnInit 被执行一次。不得不说 OnInit 的名称实在是具有误导性。
我们用一个 Demo 来证明一下
Demo
使用 angular-cli@14.2.8 创建的标准项目,下面是我的 package.json
{
"name": "my-demo",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"@angular/animations": "^14.2.0",
"@angular/common": "^14.2.0",
"@angular/compiler": "^14.2.0",
"@angular/core": "^14.2.0",
"@angular/forms": "^14.2.0",
"@angular/platform-browser": "^14.2.0",
"@angular/platform-browser-dynamic": "^14.2.0",
"@angular/router": "^14.2.0",
"rxjs": "~7.5.0",
"tslib": "^2.3.0",
"zone.js": "~0.11.4"
},
"devDependencies": {
"@angular-devkit/build-angular": "^14.2.8",
"@angular/cli": "~14.2.8",
"@angular/compiler-cli": "^14.2.0",
"@types/jasmine": "~4.0.0",
"jasmine-core": "~4.3.0",
"karma": "~6.4.0",
"karma-chrome-launcher": "~3.1.0",
"karma-coverage": "~2.2.0",
"karma-jasmine": "~5.1.0",
"karma-jasmine-html-reporter": "~2.0.0",
"typescript": "~4.7.2"
}
}
<app-my-custom [contentText]="contentText"></app-my-custom>
<button (click)="changeContent()">Change the content text</button>
import { Component, Input, OnInit, OnChanges, SimpleChanges } from '@angular/core';
@Component({
selector: 'app-my-custom',
templateUrl: './my-custom.component.html',
styleUrls: ['./my-custom.component.less']
})
export class MyCustomComponent implements OnInit, OnChanges {
@Input() contentText = '';
constructor() { }
ngOnInit(): void {
console.log('ngOnInit');
}
ngOnChanges(changes: SimpleChanges): void {
const contentText = changes['contentText'];
console.log('ngOnChanges');
console.log('isFirstChange = ', contentText.isFirstChange());
}
}
<p>my-custom works!</p>
<div>{{contentText}}</div>
我们来看一下打印出来的结果
果然 ngOnChanges 会先执行,此时的 firstChange 是 true,并且传入值已经能够被获取到了。
可以看到 OnInit 不再执行,传入值被修改,firstChange 变为 false
一些想法
那么 OnInit 是不是一点用处都没有呢?也不尽然,因为 OnInit 只会在组件实例化后被执行一次,而 OnChanges 会多次执行,所以 OnInit 不会造成死循环。
- 如果组件不需要依据传入值修改状态,那么可以使用 OnInit 对组件进行初始化
- 如果组件需要依据传入值修改状态,那么可以省略掉 OnInit 直接使用 OnChanges 进行更新
- 不要在钩子函数中发起 emit(),即便不会形成死循环也会造成性能浪费,可以将相关逻辑放入父组件执行