Angular 冷知识 —— OnInit 与 OnChange 使用的时机

对于前端工程师来说 Angular 这个名字并不陌生。虽然现在是 Vue 大行其道的时代,但是最早在国内引入前端框架,MVC,数据双向绑定等等概念的就是这个老牌的框架——Angular。即便是在现在,在面对大型项目时,Angular 也是一个非常合适的框架。尤其是在近些年 Angular 也引入了组件化的概念。

那么说到组件化就不得不提生命周期的概念了。组件包含钩子函数,可以在实例化的不同时期实现对组件的监控和修改。当我第一次接触 Angular Component 的时候对于 OnInit 和 OnChanges 有一个直观的判断,即 OnInit 进行初始化操作 OnChanges 监听传入值并依据更新后的值修改组件状态。也就是说 OnInit 先执行,OnChanges 后执行。那么事实果真如我所想吗?

OnInit & OnChanges

在 Angular 的源码中对它们有这样的描述

OnInit

A callback method that is invoked immediately after the default change detector has checked the directive's data-bound properties for the first time, and before any of the view or content children have been checked. It is invoked only once when the directive is instantiated.

翻译过来就是:

在默认更改检测器第一次检查指令的数据绑定属性之后,并且在检查任何视图或内容子项之前立即调用的回调方法。 当组件被实例化时,它只被调用一次。

所谓的第一次检查指令的数据绑定属性指的是 OnChanges 会先于 OnInit 被执行一次。

OnChanges

A callback method that is invoked immediately after the default change detector has checked data-bound properties if at least one has changed, and before the view and content children are checked.

翻译过来就是:

一种回调方法,在默认更改检测器已检查数据绑定属性(如果至少有一个已更改)之后,并且在检查视图和内容子项之前立即调用。

综上所述

从上面的描述可以看出 OnChanges 会先于 OnInit 被执行一次。不得不说 OnInit 的名称实在是具有误导性。

我们用一个 Demo 来证明一下

Demo

使用 [email protected] 创建的标准项目,下面是我的 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-component 中引入

app.component.html

<app-my-custom [contentText]="contentText"></app-my-custom>
<button (click)="changeContent()">Change the content text</button>

my-custom.component.ts

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());
  }

}

my-custom.component.html

<p>my-custom works!</p>

<div>{
   
   {contentText}}</div>

ng serve 启动后效果如下

我们来看一下打印出来的结果 

果然 ngOnChanges 会先执行,此时的 firstChange 是 true,并且传入值已经能够被获取到了。

现在点击 Change Button 

可以看到 OnInit 不再执行,传入值被修改,firstChange 变为 false

一些想法

不得不说 OnChanges 在 OnInit 之前执行的这种顺序导致 OnInit 十分的尴尬。虽然从字面上理解 OnInit 应该是组件执行初始化过程的必要钩子,但是 OnChanges 似乎能够完全替代 OnInit 。如果一定要区分是否是第一次执行可以使用 isFirstChange 进行判断。

那么 OnInit 是不是一点用处都没有呢?也不尽然,因为 OnInit 只会在组件实例化后被执行一次,而 OnChanges 会多次执行,所以 OnInit 不会造成死循环。

基于以上的特点我们可以得出结论:

  1. 如果组件不需要依据传入值修改状态,那么可以使用 OnInit 对组件进行初始化
  2. 如果组件需要依据传入值修改状态,那么可以省略掉 OnInit 直接使用 OnChanges 进行更新
  3. 不要在钩子函数中发起 emit(),即便不会形成死循环也会造成性能浪费,可以将相关逻辑放入父组件执行

猜你喜欢

转载自blog.csdn.net/KenkoTech/article/details/127750738