[Angular] Day5. Lifecycle hooks

在 Angular 的 Component 中有一个生命周期,当 Angular 实例化这个 Component 并且把它或他的 sub-component 呈现在 UI 时便会开始,而 Lifecycle 会持续的检测是否有绑定的数据被更改,因为这些绑定数据的更改可能会需要更新 UI 或 Component 的内容,而当 Angular 销毁了这个 Component 实例并从 DOM 中移除这个 Component 的 Template 後 Lifecycle 就会结束。

简单来说,当 Angular 在某一个地方的 HTML 发现了一个 Component 的 selector(身分证号码)後,便会找到这个 Component 并将他实例化和将他的 Template 呈现在 UI 上(放进 DOM 中),这个时候这个 Component 就开始了他的一生,他会在他的一生中不断的检测是否有东西需要改变,最後当使用者离开了这个页面,代表着这个 Component 不在被需要了,他就会被销毁掉结束他的一生。

https://ithelp.ithome.com.tw/upload/images/20210730/20124767kIMDJfjDfg.png


Responding to lifecycle events

了解了什麽是 Lifecycle 後,我们要正式介绍 Lifecycle 了,首先你可以透过 import angular/core 引入一个或多个 lifecycle 使用。

import { Component, OnInit, OnChanges, ... } from '@angular/core';

每一个 Lifecycle method 前面都有 ng 当作前缀字,举例来说如果要使用 OnInit 这个 Lifecycle method 则要在你的 TypeScript Class 中输入 ngOnInit( ),而当你要使用某一个 Lifycycle method 时,记得要在你的 Class 使用 implements 继承这个 Lifycycle。

import { Component, OnInit } from '@angular/core';

@Component({
  selector: 'app-first-component',
  templateUrl: './first-component.component.html',
  styleUrls: ['./first-component.component.css']
})
export class FirstComponentComponent implements OnInit {

  constructor() { }

  ngOnInit(): void {
  }

}

Lifecycle event sequence

每一个 Lifecycle method 都有对应的被调用时机,Angular 会自动在最适当的时间调用适当的 Lifecycle method,以下是 Angular 按照顺序执行的 Lifecycle method。

Name Purpose Timing
ngOnChanges() 当这制或重置绑定的数据时会被调用,比如你在这个 Component 中绑定了一个由父层传下来的数据,当这个数据发生改变(父层丢新的东西下来)时就会触发,要注意的是如果在这个 method 的逻辑没有处理好,可能会发生无限回圈的问题,所以需要特别注意。 他会在 Component 刚被创造出来就被调用一次,会比 OnInit() 还早,还有每当一个或多个数据绑定 @Input 属性发生改变时会触发。
ngOnInit() 会在 Component 完成数据绑定(ngOnChanges)後初始化 Component。 在第一个 ngOnChange() 之後触发,只会被调用一次。
ngDoCheck() 当 Angular 无法获不会自动检测到变化并采取行动,在每次更改检测运行期间被调用,会跟在 ngOnChanges() 和 ngOnInit() 後面,有点抽象我会在下面有详细解释。 在每次更改检测运行时立即在 ngOnChanges() 之後调用,在第一次运行时立即在 ngOnInit() 之後调用。
ngAfterContentInit() Angular 将 ng-content 的内容投影到 Component 的 view 後被调用 在第一个 ngDoCheck() 後被调用
ngAfterContentChecked() 每次完成 ng-content 的变更检测之後调用 在 ngAfterContentInit() 之後和每一个 ngDoCheck() 之後被调用
ngAfterViewInit() 当 Angular 初始化自身 Component和子 Component 的 view 後被调用。 在第一个 ngAfterContentChecked() 之後调用一次
ngAfterViewChecked() 每次做完自身 Component和子 Component 的变更检测後呼叫 在 ngAfterViewInit() 和每个 ngAfterContentChecked() 之後调用
ngOnDestroy() 在 Angular 销毁 directive 或 Component 之前调用,主要用於 unsubscribe Observable 以防内存泄露 在 Angular 销毁 directive 或 Component 之前立即调用

详解 ngDoCheck()

当我第一次在 Angular 官方文件中看到这个我看了十几次我都看不懂,查了一些文章後才大概了解他是在什麽时候被呼叫到,我们就来详细介绍一下吧。

在 Angular 官方文档中这样写到:

Detect and act upon changes that Angular can’t or won’t detect on its own.
Called during every change detection run, immediately after ngOnChanges() and ngOnInit()

相当抽象对吧,,虽然知道他会是在 ngOnChanges( ) 和 ngOnInit( ) 之後触发的,但是当他被触发的时候 Component 是否有被检查?而 Angular 无法自动检查是指什麽?要解答这个问题我们需要先了解什麽是 Component check,在 Component 变更检测有三个核心的操作:

  • update child component input binding ( @Input( ) )
  • update DOM interpolations ( 在 Template 中的 {{ value }} )
  • update query list

除了这些之外 Angular 还会触发 Lifecycle 作为变更检测的一部分,所以当检查父层时会触发子层的 Lifycycle,假设我们有一个这样的结构:

ComponentA
	ComponentB
		ComponentC

当 Angular 运行检测时的顺序如下:

Checking A component:
  - update B input bindings
  - call NgDoCheck on the B component
  - update DOM interpolations for component A
 
 Checking B component:
    - update C input bindings
    - call NgDoCheck on the C component
    - update DOM interpolations for component B
 
   Checking C component:
      - update DOM interpolations for component C

上面的顺序只是一个简单的顺序列表,可以看到 DoCheck 会在何时被触发。

可以看到在检查父层时子层就会调用 ngDoCheck ,假设我们在 ComponentB 使用了 onPush 会发生什麽事:

Checking A component:
  - update B input bindings
  - call NgDoCheck on the B component
  - update DOM interpolations for component A
  
 if (bindings changed) -> checking B component:
    - update C input bindings
    - call NgDoCheck on the C component
    - update DOM interpolations for component B
 
   Checking C component:
      - update DOM interpolations for component C

可以看到我们在 ComponenB 检测前加上了一个小判断,如果这个判断为 false 就会满足官方文档提到的当 Angular 无法获不会自动检测到变化并采取行动,所以即使不会检查 ComponentB 但是仍然会触发 ComponentB 上面的 ngDoCheck。

我们来透过程序码直接操作看看,首先我们先定义一个 sub-component

@Component({
  selector: 'app-first-component',
  template: '<h2>The name is: {{o.name}}</h2>',
  changeDetection: ChangeDetectionStrategy.OnPush // (1)
})

export class FirstComponentComponent implements OnChanges {
	@Input() o: { id: number; name: string; } = { id: NaN, name: '' }; // (2)

	id: number = NaN;

  ngOnChanges() {
    console.log('first-component OnChange() been call');  // (3)
    this.id = this.o.id;
  }

  ngDoCheck() {
    console.log('first-component DoCheck() been call');  // (4)
    if (this.id !== this.o.id) {
      this.cd.markForCheck();
    }
  }
}
  • (1):利用 OnPush 来变更检测策略。
  • (2):使用 @Input( ) 绑定一个 input binding
  • (3):当 @Input( ) 发生改变时,触发 sub-component 中的 ngOnChanges
  • (4):当父层发生变化时,触发 sub-component 的 ngDoCheck

接着我们在父层将 o 这个物件向下传给 sub-component,在2秒内他通过更新 name 和 id 来改变这个物件

@Component({
  selector: 'app-root',
  template: `
		<h1>Hello {{name}}</h1>
    <a-comp [o]="o"></a-comp>  // (1)
	`
})
export class AppComponent implements OnInit{
  o = {id: 1, name: 'Fandix'};

  ngOnInit() {
    setTimeout(() => {         // (2)
      this.o.id = 2;
      this.o.name = 'Jane';
    }, 2000);
  }
}
  • (1): 将 o 向下传递给 sub-component
  • (2):利用 setTimeout 在经过两秒後更改 o 的 property

https://ithelp.ithome.com.tw/upload/images/20210730/20124767T4wP0YOgSv.png

由於 Angular 追踪的是 o 这个物件的 reference,所以当我们以不更改 reference 的情况下改变 o 的内容就不会被 Angular 检测到,这就是为什麽 sub-component 的 ngOnChanges() 只有被呼叫一次(创建 Component 时第一次呼叫),但是经过上面的解说後可以知道,就算 sub-component 不会被 Angular 给自动检测,但是由於父层发生了改变就会让子层的 ngDoCheck 自动被呼叫到,所以在 AppComponent 发生改变时 sub-component 的 ngDoCheck 依然会被触发,这就是当 Angular 无法或不会自动检测到变化并采取行动的意思。


Initializing a component or directive

大概介绍完 Angular 的 Lifecycle,我们来看看 ngOnInit 的使用场景。

  • 在 construstor 之外执行逻辑复杂的初始化任务,如果是 fetch data 就不应该在 constructor 做而应该要在 ngOnInit 处理。
  • 可以在 ngOnInit 中设置输入属性,constructor 应该只将初始局部变量设置为简单的值,所以如果你需要根据 @Input() 的值来设定内容的话,需要在 ngOnInit 中处理。

Cleaning up on instance destruction

在开发 Angular component 时可以将清理的逻辑放在 ngOnDestroy( ) 中,这些逻辑必须在 Angular 销毁这个 Component 之前执行,这里是释放不会自动释放资源的地方,如果一直建立不会自动释放资源的东西最後却没有将他清理掉,可能会造成内存泄漏或满载的问题,这里可以用来:

  • Unsubscribe Observable 和 DOM event
  • 停止 interval timers
  • Unregister 所有影响到别的地方的 callback function

结论

在本章中我们谈到了 Angular 的 Lifecycle,了解有哪些 Lifecycle method 可以使用以及他们被调用的时机点,掌握好如何在对的时机点使用对的 Lifecycle method 是非常重要的,如果在错的时间使用错的 Lifecycle method 会造成非预期的错误,下一章将介绍在 Angular 中父子层 Component 之间是如何共享数据的,虽然在本章节有提到一点点( @Input( ) ),不过将会在下一张更详细的说明,那我们下一章再见。


Reference


<<:  [披萨吃到饱-1] 义米兰手作披萨 #如何不花钱找到观众

>>:  30天轻松学会unity自制游戏-制作Player

[Day22] 发送验证信API – views

嗨嗨~~ 夥伴们,大家好,今天我们要来说明的是,发送验证信API的逻辑,以下是我的程序码 程序码 f...

IOS、Python自学心得30天 Day-5 TensorFlow 建立和训练模型

前言: 再来就是建立和训练模型 程序码: 方案一 model = tf.keras.Sequenti...

【第四天 - HG 泄漏】

Q1. HG 是什麽? Mercurial 是一种轻量级分散式版本控制系统,由於 Mercuial ...

17. STM32-I²C EEPROM

上篇针对AT24C256B DataSheet当中的地址以及功能说明,这一篇会使用STM32去对EE...

Reader 的 MockK 测试

Reader 是我们 Android library 里面最外层的 API ,要测试它要先考虑它有跟...