Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

这篇具有很好参考价值的文章主要介绍了Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言

之前在 Component 组件 の Angular Component vs Custom Elements 文章中,我们有学习过几个基础的 Lifecycle Hooks。

比如 OnChanges、OnInit、AfterViewInit、OnDestroy,但那篇只是微微带过而已。

这篇让我们来深入理解 Angular 的 Lifecycle Hooks。

 文章来源地址https://www.toymoban.com/news/detail-837831.html

介绍

在 Component 组件 の Dependency Injection & NodeInjector 文章中,我们看见了组件从无到有的创建与渲染过程。

整个过程可以被分解成多个阶段,每一个阶段的组件都处于不同的形态。

比如

A 阶段,组件只是个 Definition。

B 阶段,组件已经被实例化,但是 Template 中的子组件尚未被实例化。

C 阶段,ViewModel 已经更新到 DOM 上。

等等

组件生命周期钩子的目的就是让我们有机会去拦截上面这些阶段,然后对组件做一些事情。

Angular 一共提供了 8 个钩子,它们是(排名不分先后):

  • OnInit
  • OnChanges
  • DoCheck
  • AfterContentInit
  • AfterContentChecked
  • AfterViewInit
  • AfterViewChecked
  • OnDestroy

如果加上组件 constructor 那就是 9 个钩子。

 

Constructor 阶段

想要深入理解组件生命周期钩子,最简单的方法自然是逛源码咯。

经历过了 Change Detection 和 NodeInjector 两篇文章。我相信大家对 bootstrapApplication 函数已经不陌生了,那我们直接进入重点吧。

组件实例化发生在什么时候?

bootstrapApplication 有两大环节,一个是 create,一个是 update。

所有的组件都会在 create 阶段被实例化。

ApplicationRef.bootstrap 方法中有这么一段

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

这里的重点是,所有组件(App 和其后裔)实例化都是在 create 阶段完成的。在进入 update 阶段之前,所有组件都是已经实例化好的了。

我们进入 ComponentFactory.create 方法看看 create 阶段实例化组件的细节。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

renderView 函数的源码在 render.ts

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

所谓的 render 就是执行这个组件的 template 方法,这个方法会实例化所有的子组件。

这里的重点是,先实例化完所有子组件,然后才继续逐个 render 子组件。

renderChildComponents 函数

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

renderComponent 函数

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

组件实例化的顺序

依据上面的源码,它有 3 个重要的步骤

  1. 实例化组件

  2. render 组件 (也就是执行组件 template 方法,这方法会实例化所有子组件)。

  3. 所有子组件实例化好了后,再逐个 render 子组件,这里就开始递归了,一直到所有后裔组件实例化完成。

我们来看个复杂点的例子

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

相同颜色表示它们在同一个组件 Template 里,号码表示组件被实例化的顺序。

我们需要搞清楚什么时候在同一层,什么时候去下一层。

1. 相同颜色的,一定是连贯的顺序

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

因为实例化子组件时,是不会进入下一层孙组件的,只有等到所有子组件实例化好后,才会往下一层孙组件走。

2. 路线图

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

 

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

constructor 阶段可以做什么?

constructor 的特别之处在于它是 injection context,所以它可以使用 inject 函数。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

其它 Lifecycle Hooks 都不行哦。

我们通常会在 constructor 阶段注入组件需要的 Service,把它们存起来,供之后使用。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

constructor 阶段不可以做什么?

constructor 阶段,@Input 属性值还没有被填写,它是不可用的。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

 

8 大组件生命周期钩子 Overview

我们先 overview 这 8 个生命周期钩子:

  1. 8 个 Lifecycle Hooks 全部发生在 refreshView 函数中,除了 DestroyHooks。(大名鼎鼎的 refreshView 函数在 Change Detection 文章中就介绍过了,不熟悉的请回去温习)

  2. refreshView 会遍历所有组件,只要中途没有中断,所有组件的 Lifecycle Hooks 都有机会触发,当然如果中途断了,那剩余的组件就不会触发任何 Lifecycle Hooks 了。

  3. OnInitAfterContentInitAfterViewInit 只会在第一次 refreshView 时触发,往后就不再触发了。

  4. OnChanges 在每一次 refreshView 时,只要有 @Input 并且值发生了变化就会触发 (第一次赋值也算值变化),如果没有变化就不会触发。

  5. DoCheckngAfterContentCheckedngAfterViewChecked 在每一次 refreshView 都会触发。

  6. OnDestroy 只会在组件被销毁时触发,也只会触发一次。(我们还没有学到如何销毁组件,这个之后在 Dynamic Component 章节才会教)

 

PreOrderHooks (OnChanges, OnInit, DoCheck)

8 个 Lifecycle Hooks 全部发生在 refreshView 函数中,处了 DestroyHooks,那我们就从 refreshView 函数源码开始吧。

下面会有 4 中情况,我会一起讲,虽然可能有点乱,但没办法,分开讲篇幅会很长,很啰嗦。

  1. refreshView(rootLView) 从 Root LView 开始

  2. refreshView(appLView) 从 App LView 开始

  3. 第一次 refreshView

  4. 第 n 次 refreshView

refreshView 函数 の template 方法

refreshView 源码在这里 change_detection.ts

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

App 组件长这样

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

app.component.js

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

refreshView 第一件事是去执行 LView 的 template 方法,这个方法指的是组件 Definition 中的 template 方法 (上图)。

这个 template 方法,我们已经研究过许多次了。

create mode 主要负责实例化子组件,创建它们的 TNode、TView、LView、NodeInjector 等等。

update mode 主要负责把 ViewModel 和 binding syntax 结合,也就是 "更新 DOM" 啦。

此外 update mode 里还多了 2 个以前没有学过的新代码 ɵɵproperty 和 ɵɵadvance,我们来看看它们是做什么的。

ɵɵproperty 函数

ɵɵproperty 的作用是把 ViewModel binding 到子组件的 @Input 属性上。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

ctx 就是组件实例,也就是所谓的 ViewModel 啦。

ɵɵadvance 函数

ɵɵadvance 有 2 个功能,一个是移位。一个是执行 PreOrderHooks。

移位

移位的概念是这样的:

假设 Template 长这样

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

compile 以后

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

有箭头的地方是有 binding syntax 的。坐标是 index 1、8、10、17。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

它的过程是:

移位到 index 1 > 更新 DOM >

移位到 index 8 > 填 @Input 给 C1 组件 >

移位到 index 10 > 更新 DOM >

移位到 index 17 > 填 @Input 给 C2 组件。

PreOrderHooks

ɵɵadvance 函数源码在这里 advance.ts

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

在移位之前,它会先执行 PreOrderHooks。

如果是第 1 次 refreshView,那会执行  tView.preOrderHooks。

如果是第 n 次 refreshView,那会执行 tView.preOrderCheckHooks。

我们来看看 TView 里的 preOrderHooks 和 preOrderCheckHooks 是指哪一些 Hooks。

源码来到 getNodeInjectable 函数(下面这个过程是在 template 方法 create mode 时发生的)

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

getNodeInjectable 主要任务是实例化组件,然后它会把组件的 PreOrderHooks register to TView。

registerPreOrderHooks 函数的源码在 hooks.ts

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

这里的重点是 TView 指的是谁?

假设,当前实例化的组件是 C1 组件,那 C1 的 PreOrderHooks 会被 regsiter 到 C1 的 parent TView,也就是 App TView。

同理,如果当前实例化的组件是 App 组件,那 App 的 PreOrderHooks 会被 register 到 App 的 parent TView,也就是 Root TView。

这个概念和组件 providers 是一样的,组件 providers 也是保存到 parent TView 中。

举例

下图是 C1 和 C2 组件,它们都有 OnInit Hook。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

下图是 App Template

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

C1 和 C2 组件的 OnInit Hook 会被 register 到它们的 parent TView(也就是 App TView)。

下图是 App TView

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

里面有 2 个 OnInit Hook。其它位置的号码,我们不需要理会。

小总结

refreshView 会执行组件 template 方法 (update mode)

template 方法内有 ɵɵproperty 和 ɵɵadvance 函数。

ɵɵproperty 负责填写子组件的 @Input

ɵɵadvance 负责执行子组件的 PreOrderHooks 和移位。

回到 App template 方法

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

上面有 2 个重点

  1. 当子组件执行 PreOrderHooks 时,父组件的 template 方法是还没有执行完的。

    比如上面,C1 OnInit 时,App 还没有更新 value3 到 DOM,也还没有填写 value4 到 C2 的 @Input。

  2. template 方法的结尾不是 ɵɵadvance。

    所以当 template 方法执行完,C1 PreOrderHooks 是执行了的,但是 C2 PreOrderHooks 却还没有执行。

    C2 好像是被落下了。

refreshView 函数 の PreOrderHooks

上面提到,C2 PreOrderHooks 被落下了。我们继续看 refreshView 函数。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

在执行完组件 template 方法后,接着就执行 PreOrderHooks 了。这个环节和 ɵɵadvance 里的几乎是一样的,只是它没有指定 index。

至此,所有子组件的 PreOrderHooks 都执行完了。

PreOrderHooks 可以 / 不可以做什么?

PreOrderHooks 阶段,组件(e.g. C1)所有的 @Input 都已经赋值。

此时我们可以修改组件的属性,因为组件的 template 还没有执行。

但是,最好不要修改父组件(App)的属性了,因为这个时候,父组件的 template 已经开始执行了,

某些属性或许已经被用于 DOM 更新了。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

总之,best practice 是不要在 PreOrderHooks 里修改祖先组件的属性。

OnChanges 阶段可以做什么?

OnChanges 类似于 Custom Elements 的 attributeChangedCallback。

在每一次 refreshView 时,只要有 @Input 并且值发生了变化就会触发 (第一次赋值也算值变化),如果没有变化就不会触发。

export class C1Component implements OnChanges {
  @Input()
  value!: string;

  ngOnChanges(changes: SimpleChanges): void {
    const valueChange = changes['value'];
    if (valueChange.firstChange) {
      console.log('before after', [
        valueChange.previousValue, // undefined
        valueChange.currentValue, // value 2
      ]);
    }
  }
}

OnInit 阶段可以做什么?

我们大部分逻辑代码都会写在这里。

DoCheck 阶段可以做什么?

之前在 Change Detection 文章中讲解过了,这里就不复述了。

 

ContentHooks (AfterContentInit, AfterContentChecked)

Content 指的是 Content Projection (a.k.a slot / transclude / ng-content)。

refreshView 函数 の ContentHooks

同样是 refreshView 函数,在执行组件 template 方法 > PreOrderHooks 之后,就到了 ContentHooks。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

detectChangesInEmbeddedViews 是 Dynamic Component 章节的内容,之后会教。

refreshContentQueries 是 Query Elements 章节的内容,之后会教。

和 PreOrderHooks 一样,ContentHooks 也都放在 TView 里。

我们看看 tView.contentHooks 是什么时候被赋值的。

下图是 App Template

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

compile 以后

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

子组件的 ContentHooks 是在 ɵɵelementEnd 函数中被 register 到 TView 的。

注:ɵɵelement 函数结尾也是有调用 ɵɵelementEnd 哦

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

ɵɵelementEnd 函数

ɵɵelementEnd 函数源码在 element.ts

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

registerPostOrderHooks 函数源码在 hooks.ts

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

PostOrderHooks Registration 小细节

1. PostOrderHooks 包含 ContentHooks,ViewHooks 还有 DestroyHook,它们都是在这个环节 register 的。

2. Register PreOrderHooks 是发生在单个组件或指令实例化后,所以每一次只 register 一个组件或指令。
Register PostOrderHooks 是发生在 1 个 TNode 内的组件和指令全部实例化之后,所以每一次是把 TNode 内的组件和指令全部都 register。

3. Register PreOrderHooks 的顺序

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

顺序是 C2 -> C1 -> C3 

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)
Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

和 PreOrderHooks 的区别

PostOrderHooks 和 PreOrderHooks 非常相似,都是在组件 template 方法 create mode 阶段把 Hooks register 到 TView。

都是在 template 方法 update mode 时执行。

比较大的区别是 register 的顺序

PreOrderHooks 很简单,上到下,组件或指令实例化之后就 register。

PostOrderHooks 则多了一个里到外的概念,因为它是依据 ɵɵelementEnd 的位置。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

比如上图,从上往下看,第一个 element end 是 </app-c2>,然后是 </app-c1> 最后才是 </app-c3>,所以顺序是 C2 > C1 > C3。

另外一点是,子组件(e.g. C1)PreOrderHooks 执行时,组件(App)template 方法是还没有执行完的,它有一半一半的概念,

PostOrderHooks 就没有这个概念,在子组件(C1)PostOrderHooks 执行时,(App)template 方法是已经执行完的了。

PostOrderHooks 可以 / 不可以做什么?

绝对不可以修改祖先组件的属性,因为祖先组件的 template 方法都已经执行完了,DOM 都更新完了。

ContentHooks 阶段可以做什么?

提醒:ngAfterContentInit 只会触发 1 次,AfterContentChecked  每一次 refreshView 都会触发。

ngAfterContentInit 阶段我们可以 Query Content,比如 C1 组件 query C2 组件。(注:Query Elements 下一篇才会教)

然后可以修改 C2 的属性,因为这时 C2 还没有执行 template 方法,DOM 还没有更新,所以还能改。

关于 Query Content 的细节,下一篇会教,这里我们只要知道这个 Hooks 可以用于处理 Content Projection 里的内容就好了。

 

ViewHooks (AfterViewInit, AfterViewChecked)

View 指的是 LView。

refreshView 函数 の ViewHooks

同样是 refreshView 函数,在执行组件 template 方法 > PreOrderHooks > ContentHooks 之后就到了 ViewHooks。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

executeViewQueryFn 是 Query Elements 章节的内容,之后会教。

ViewHooks register to TView 这个过程,上面讲解 ContentHooks 时已经讲过了,它们都属于 PostOrderHooks,都是在 ɵɵelementEnd 时一起 register 的。

和 ContentHooks 的区别

最重要的一点是,在执行子组件 ViewHooks 前,会先递归 refreshView(子组件)

例子说明

下图是 App Template

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

PreOrderHooks 的顺序是:C1 > C2 > C3

这时 App template 方法已经执行完了,App DOM 已经更新。

ContentHooks 的顺序是:C2 > C1 > C3

这时 C1、C2、C3 的 template 方法都还没有执行,DOM 还没有更新。

在执行 ViewHooks 之前,先递归 refreshView(C1)、refreshView(C2)、refreshView(C3)

假设 C1、C2、C3 Template 里都没有子组件,那它们 TView 自然也没有 Hooks,那执行完 template 方法后就返回到了 refreshView(App)。

接着就到执行 ViewHooks 了,ViewHooks 的顺序和 ContentHooks 一样 (因为它们是一起 register 的):C2 > C1 > C3

结束

ViewHooks 阶段可以做什么?

当一个组件的 ViewHooks 触发时,它的祖先,它自己,它的后裔,所有组件的 template 方法都已经执行完了,DOM 也更新完了。

通常这个时候只会做一些 Query Element + DOM manipulation。

 

DestroyHooks (OnDestroy)

Dynamic Component 章节会教。

 

Example

以前写的一些例子 Github – angular-blog-component-lifecycle-hooks。

结构

<!-- app.html -->
<app-child>
  <app-transclude-to-child></app-transclude-to-child>
</app-child>

<!-- child.html -->
<app-inside-child></app-inside-child>
<ng-content></ng-content>

<!-- inside-child.html -->
<p>inside-child works!</p>

<!-- transclude-to-child.html -->
<app-inside-transclude-to-child></app-inside-transclude-to-child>

<!-- inside-transclude-to-child.html -->
<p>inside-transclude-to-child works!</p>

结果

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

 

AfterRenderHooks (afterNextRender, afterRender)

Angular 在 v16.2 推出了新的 AfterRenderHooks,目前 (v17 版本) 这 2 个 Hooks 任处于 Developer Preview。

不过,依据目前的线索,这或许是未来 Angular 的 Lifecycle Hooks 方向哦。大家还是趁早多了解一下。

afterRender 函数

export class AppComponent {
  constructor() {
    afterRender(() => {
      console.log('Hello World');
    });
  }
}

AfterRenderHooks 的使用方式和其它 Lifecycle Hooks 截然不同。

它是通过 afterRender 函数来 register Hooks 的。

这个 afterRender 函数依赖 Injector,所以必须在 injection context (e.g. constructor) 内才可以使用。

afterRender 源码逛一逛

我们直接看源码了解细节呗。

afterRender 函数的源码在 after_render_hooks.ts

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

三大重点:

  1. afterRender 依赖 Injector

  2. afterRender 只在 browser 执行,Server-side Render (SSR) 是不执行的。

  3. afterRender 可以被取消

以上三个概念,其它 Lifecycle Hooks 都没有。

afterRender 会把 callback 存放到一个全局变量里,等 Lifecycle 到指定时刻,所有 callback 会被拿出来调用。简单说就是一个典型的观察者模式。

afterNextRender

afterNextRender 和 afterRender 的区别类似于 AfterViewInit 和 AfterViewChecked

afterNextRender 只会执行一次。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

AfterRenderHook 什么时候执行?

我们先来温习一下 Change Detection 机制。

ApplicationRef.tick > ViewRef.detectChange > refreshVIew > 递归 refreshVIew

PreOrderHooks、PostOrderHooks 都发生在 refreshView 内。

而 AfterRenderHooks 则是在 detectChange 的结尾,也就是所有 refreshView 之后。

detectChangesInternal 函数源码在 change_detection.ts

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

AfterRenderEventManager.end 方法源码在 after_render_hooks.ts

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

AfterRenderHook 的执行顺序

First in first out

Hooks 的执行顺序是 first in first out,先 register 的就先执行。

register 发生在 construtor,所以 App AfterRenderHooks 会先于 Child AfterRenderHooks。

这一点和 ViewHooks 是不同的哦,ViewHooks 是从底层到上层,AfterRenderHooks 是上层到底层。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

AfterRenderPhase

除了 first in first out 概念,AfterRenderHooks 还分 phrase (阶段)。

有 4 个 phrase。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

phrase 执行的顺序是 EarlyRead > Write > MixedReadWrite > Read。

综合 first in first out 和 phrase 概念,最终的执行顺序是这样。

  1. 先把 phrase: EarlyRead 所有 Hooks 按 register first in first out 执行一遍

  2. 再把 phrase: Write 所有 Hooks 按 register first in first out 执行一遍

  3. 再把 phrase: MixedReadWrite  所有 Hooks 按 register first in first out 执行一遍

  4. 最后是把 phrase: Read 所有 Hooks 按 register first in first out 执行一遍

Nested Hooks

下面这个叫 Nested Hooks

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

最好不要搞这么绕...

我们这样来理解啊,

假设一开始有 100 个 AfterRenderHooks,在 refreshView 执行完之后,开始 foreach 100 个 hooks,

依据 phrase 和 first in first out 顺序。执行到一半,比如第 50 个出现了 nested hook,此时会把这个 nested hook 先 hold 着,一直到 100 个 hooks 执行完。

假设,一共 hold 了 10 个 nested hooks,那这个时候才 register 这 10 个 hooks,总数变成 110 个 hooks。

接着就等下一轮 detectChange 了。(重点:这一轮只执行 100 hooks,10 nested hooks 只是 register 而已没有执行,等下一轮才执行 110 hooks)

AfterRenderCallbackHandlerImpl 源码

关于 First in first out,Phrase,Nested Hooks 的相关源码在 after_render_hooks.ts

class AfterRenderCallbackHandlerImpl

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

Nested Hooks 自动执行?

上面源码显示,Nested Hooks 会在下一轮 detectChange 才被执行,但如果我们做测试的话,会发现它视乎是在同一轮 detectChange 执行的。

其实这是我们的错觉,Angular 调用 tick 比我们想象中的频密。

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

所以它确实是下一轮 detectChange 才执行的。

AfterRenderHooks vs ViewHooks

写法上的区别

PreOrderHooks 和 PostOrderHooks 的写法偏向面向对象,一个 interface 配一个对象方法。

AfterRenderHooks 则有点函数式的味道,没有 interface,不需要对象方法,直接调用一个全局函数,这个和 inject 函数就很像。

Angular 目前的方向是减少面向对象,增加函数式,弃 decorator。

未来 Signal-based Component 可能会统一使用 AfterRenderHooks 的写法。相关参考:Github – [Complete] Sub-RFC 3: Signal-based Components

功能上的区别

AfterRenderHooks 依赖 Injector。

AfterRenderHooks 只在 browser 会执行,Server-side Render (SSR) 不执行。

ViewHooks 的执行顺序是依据组件,从内到外或者从底层到上层,一遍完。

AfterRenderHooks 的执行顺序分 4 个 phrase,所以会跑 4 轮,每一轮依据 register 顺序 first in first out 来执行。

所以 AfterRenderHooks 其实比 ViewHooks 复杂和灵活。

用哪个?

旧代码可以不必急改,但新代码推荐使用新的方式。我目前是没有看到有什么是 ViewHooks 能做但 AfterRenderHooks 做不了的,所以敢敢用呗。

 

目录

上一篇 Angular 17+ 高级教程 – Component 组件 の Dependency Injection & NodeInjector

下一篇 Angular 17+ 高级教程 – Component 组件 の Query Elements

想查看目录,请移步 Angular 17+ 高级教程 – 目录

 

 

 

 

 

TODO,下面是以前写的稿,过阵子就可以洗掉了。

 

前言

我们在 这篇 和 这篇 中已经学习了几个基本的 Lifecycle Hooks. 分别是

constructor

OnInit

AfterContentInit

AfterViewInit

OnDestroy

OnChanges

这篇我们会把其余的 Lifecycle Hooks 都学完.

 

Init Group and Changes Group

Angular 的 Lifecycle 可以分为两组.

第一组有 constructor, OnInit, AfterContentInit, AfterViewInit, OnDestroy

第二组有 OnChanges, DoCheck, AfterContentChecked, AfterViewChecked

在 first time render 时, 上面两组都会跑. 但绝大部分情况下我们关心第一组就可以了.

而在 event 发生后, 我们更多的是会用到第二组.

所以我喜欢把它们分开两组看待.

 

Init Group Lifecycle Hooks (组件内视角)

在一个组件内部观察的话, Lifecycle Hooks 的顺序是这样的

constructor > OnInit > AfterContentInit > AfterViewInit > OnDestroy

下面我讲一下各个 hook 的特点

constructor 

1. 这个阶段 @Input 还没有被赋值, 都是 undefined. 

2. 这个阶段 inject parent 已经拿到组件实例了, 但这个实例的 @Input 也是 undefined. 

3. 这个阶段 query child 是连组件实例都没有的. 空空如也

4. 这个阶段, 组件 template 还没有 append 到 document

OnInit

1. 这个阶段 @Input 已经有值了.

2. 通常我们会在这个阶段去 ajax 什么的, 大部分代码都会写在这个阶段里.

3. 如果 @ViewChild / @ContentChild 配置 option static : true, 那么这个阶段已经可以拿到组件实例了, 但是这个组件是还没有 OnInit 的 (它的 @Input 都是 undefined)

4. 这个阶段组件 template 已经 append 到 document 了, 但是涉及 binding 的部分都没有 render 好 (empty)

AfterContentInit

1. 这个阶段 @ContentChild 已经可以拿到组件实例了, 而且这个组件已经 OnInit 好了 (但还没有 AfterViewInit 哦)

AfterViewInit

1. 这个阶段组件 template binding 的部分都 render 好了.

2. 通常这个阶段我们可以去 read DOM dimension, 比如 window.getComputedStyle 之类的

3. 这个阶段我们不应该再去修改 view model 了, 如果要修改那样配上 requestAnimationFrame, 让 Angular 进入下一个渲染周期 (这个是 Change Detection 概念, 这里不过多展开)

OnDestroy

1. 这个阶段 DOM 已经被移除了.

2. 通常我们在这个阶段释放资源, 比如 RxJS unsubscribe 等等.

 

Init Group Lifecycle Hooks (组件外视角)

上一 part 我们是站在一个组件内的视角去看 lifecycle hooks. 现在我们从外部看多个组件它们 lifecycle hooks 的顺序是怎样的.

首先我们有 5 个组件

App, Child, InsideChild, TranscludeToChild, InsideTranscludeToChild

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

all html

<!-- app.html -->
<app-child>
  <app-transclude-to-child></app-transclude-to-child>
</app-child>

<!-- child.html -->
<app-inside-child></app-inside-child>
<ng-content></ng-content>

<!-- inside-child.html -->
<p>inside-child works!</p>

<!-- transclude-to-child.html -->
<app-inside-transclude-to-child></app-inside-transclude-to-child>

<!-- inside-transclude-to-child.html -->
<p>inside-transclude-to-child works!</p>

source 在这里 Github – repository

先了解组件结构

app > child > inside child 这 3 个就是一层一层进, 

transclude-to-child 最特别, app.html 把 transclude-to-child 组件 transclude 到 child 组件.

最后 transclude-to-child > inside-transclude-to-child 就是进一层.

constructor, OnInit, AfterContentInit, AfterViewInit 的顺序.

constructor 环节

第一个环节是 constructor. Angular 会把所有组件都实例化出来先.

1. app constructor

然后进入 app.html

<app-child>
  <app-transclude-to-child></app-transclude-to-child>
</app-child>

接着

2. child constructor

3. transclude to child constructor

接着进入 child.html

<p>child works!</p>
<app-inside-child></app-inside-child>
<ng-content></ng-content>

4. inside child constructor

进入 inside-child.html

<p>inside-child works!</p>

这里没有组件需要实例化了. 返回最外面 app,html

<app-child>
  <app-transclude-to-child></app-transclude-to-child>
</app-child>

进入 transclude-to-child.html

<p>transclude-to-child works!</p>
<app-inside-transclude-to-child></app-inside-transclude-to-child>

5. inside transclude to child constructor

进入 inside-transclude-to-child.html

<p>inside-transclude-to-child works!</p>

没有组件需要实例化了. 

至此 constructor 环境结束. 以下是组件 constructor 的 console

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

OnInit > AfterContentInit > AfterViewInit 环节

和 constructor 不同, Angular 不会先把所有组件 OnInit 一遍才 AfterContentInit. OnInit 和 AfterContentInit 是前脚后脚一起的, 看下面例子理解.

1. app init

<body>
  <app-root></app-root>
</body>

由于 app-root 没有任何的 transclude, 所以 app 组件 AfterContentInit 紧跟着 OnInit 后就触发

2. app after content init

进入 app.html

<app-child>
  <app-transclude-to-child></app-transclude-to-child>
</app-child>

3. child init

由于 child 有 transclude 所以它的 AfterContentInit 不会马上触发.

4. transclude to child init

transclude to child 没有 transclude 所以 AfterContentInit 马上触发

5. transclude to child after content init

此时 child 的 transclude 完成了, 所以到它的 after content init 

6. child after content init

进入 child.html

<p>child works!</p>
<app-inside-child></app-inside-child>
<ng-content></ng-content>

7. inside child init

8. inside child after content init

进入 inside-child.html

<p>inside-child works!</p>

没有组件了, 所以开始 render view, 并且触发 AfterViewInit

9. inside child after view init

到目前为止的 console

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

回到 child.html

<p>child works!</p>
<app-inside-child></app-inside-child>
<ng-content></ng-content>

before render child component, 要先处理好 ng-content

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

 

这个 ng-content 内容是 transclude-to-child, 它已经 after content init 了.

进入 transclude-to-child.html

<p>transclude-to-child works!</p>
<app-inside-transclude-to-child></app-inside-transclude-to-child>

10. inside transclude to child init 

11. inside transclude to child after content init

进入 inside-transclude-to-child.html

<p>inside-transclude-to-child works!</p>

没组件了, render view

12. inside transclude to child after view init

回到 transclude-to-child.html

<p>transclude-to-child works!</p>
<app-inside-transclude-to-child></app-inside-transclude-to-child>

没有组件了, render view

13. transclude to child after view init

回到 child.html

<p>child works!</p>
<app-inside-child></app-inside-child>
<ng-content></ng-content>

ng-content 也完成了, 现在 render view

14. child after view init

回到 app.html

<app-child>
  <app-transclude-to-child></app-transclude-to-child>
</app-child>

15. app after view init 

最后的 console

Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)

 

Changes Group Lifecycle Hooks

我建议大家先学 Change Detection 在回来看这个部分. 

虽然 Changes Group Lifecycle Hooks 在 first time render 也会触发, 但很少会要用到, 

所以这里我 focus 的是后续 event 触发的 lifecycle.

DoCheck

DoCheck 发生在 parent 组件 detectChanges 之后.

比如 ApplicationRef.tick 会导致 AppComponent DoCheck 触发. 

如果 AppComponent 也被 detectChanges, 那么其每一个子组件都会进行 DoCheck, 以此类推.

通常我们会利用这个 DoCheck 判断这个组件需不需要 detectChanges, 如果需要那我们就 markForCheck 它, 让这个 detectChanges 机制从上到下持续执行.

通常我们不会在这个阶段去修改 view model, 当然你要改也是可以的. 它还没有到渲染期.

OnChanges

1. 每当 @Input 改变, 它就会触发, 包括 first time render (input from undefined to value) 也会.

2. 这个阶段我们任然可以去修改 view model.

AfterContentChecked

当 transclude 的内容渲染完成后触发.

如果我们组件内依赖 transclude 内容做一些事情. 那就可以在这个期间去 update 自生的 view model. 

这个期间可以修改当前组件的 view model, 但是不能修改 transclude 进来组件的 view model 哦, 因为 transclude 的部分已经渲染完成了.

AfterViewChecked

顾名思义, 就是当前组件渲染完毕后触发.

通常我们会在这个期间去做一些 DOM manipulation (比如获取 div 宽度/高度, 做一些设计).

这时不可以去修改 view model 了. 如果想修改, 那就需要用 window.requestAnimationFrame 让它触发下一次循环了. (通常不会有这种需求啦)

 

到了这里,关于Angular 17+ 高级教程 – Component 组件 の 生命周期钩子 (Lifecycle Hooks)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Angular系列教程之生命周期钩子

    Angular是一种流行的前端开发框架,它提供了许多功能强大且易于使用的工具和特性。其中之一就是生命周期钩子(Lifecycle Hooks),它们允许我们在组件的不同生命周期阶段执行自定义代码。本文将介绍Angular的生命周期钩子以及如何使用它们。 生命周期钩子是一些用于在组件

    2024年01月17日
    浏览(44)
  • Angular--父子组件生命周期钩子(lifecycle hooks)执行过程

    组件初始化过程中,生命周期钩子执行顺序: constructor()构造函数,初始化class,(constructor不属于Angular生命周期钩子的范畴,这里只是说明组件组件初始化会先调用构造函数)。 ngOnChanges()--如果组件没有输入属性(@Input()),或者使用时没有提供任何输入属性,那么angular不会调用它

    2024年01月20日
    浏览(52)
  • Angular 17+ 高级教程 – Component 组件 の Structural Directive (结构型指令) & Syntax Reference (微语法)

    在 Attribute Directives 属性型指令 文章中,我们学习过了指令。指令是没有 HTML 和 CSS 的组件,它单纯用于封装 JS 的部分。 这一篇我们将继续学习另一种指令 -- Structural Directive 结构型指令。 就代码而言,Structural Directive 和 Attribute Directives 是完全一样的,只是用途不同,因此

    2024年03月10日
    浏览(49)
  • Ionic4 生命周期钩子函数和angular生命周期钩子函数介绍

    Ionic 4(以及之后的 Ionic 版本)使用了 Angular 生命周期钩子,因为 Ionic 是基于 Angular 构建的。因此,Ionic 4 中的生命周期与 Angular 组件生命周期非常相似。以下是一些常见的 Ionic 4 生命周期钩子: ionViewDidLoad : 在页面加载完成后触发。通常用于执行一次性的初始化任务。不推

    2024年02月07日
    浏览(54)
  • 计算属性和监听属性,生命周期钩子,组件介绍

    # 计算属性是基于它们的依赖进行缓存的 # 计算属性只有在它的相关依赖发生改变时才会重新求值 # 计算属性就像Python中的property,可以把方法/函数伪装成属性 # 计算属性必须要有返回值 基本使用 首字母变大写 通过计算属性,重写过滤案例 只要属性发生变化,就会执行 函数

    2024年01月21日
    浏览(43)
  • Angular组件生命周期详解

    当 Angular 实例化组件类 并渲染组件视图及其子视图时,组件实例的生命周期就开始了。生命周期一直伴随着变更检测,Angular 会检查数据绑定属性何时发生变化,并按需更新视图和组件实例。当 Angular 销毁组件实例并从 DOM 中移除它渲染的模板时,生命周期就结束了。当 Ang

    2024年02月05日
    浏览(45)
  • 微信小程序:uni-app页面Page和组件Component生命周期执行的先后顺序

    文档 页面生命周期 https://uniapp.dcloud.net.cn/tutorial/page.html#lifecycle 组件生命周期 https://uniapp.dcloud.net.cn/tutorial/page.html#componentlifecycle 经测试,得出结论: H5和微信小程序的生命周期函数调用顺序不一致 一般情况下,主要使用的周期函数如下,他们的执行顺序是固定的 页面 组件

    2024年02月08日
    浏览(58)
  • Vue2-replace属性、编程式路由导航、缓存路由组件、两个新的生命周期钩子、路由守卫、路由器工作模式

    🥔:如果事与愿违,那一定是上天另有安排 更多Vue知识请点击——Vue.js 1.作用:控制路由跳转时操作浏览器历史记录的模式 2.浏览器的历史记录有两种写入方式:分别为 push 和 replace , push是追加历史记录,replace是替换当前记录 。路由跳转时候 默认为push 3.如何开启replace模

    2024年02月10日
    浏览(50)
  • Angular 17+ 高级教程 – HttpClient

    HttpClient 是 Angular 对 XMLHttpRequest 和 Fetch 的封装。 HttpClient 的 DX (Developer Experience) 比 XMLHttpRequest 和 Fetch 都好,只是学习成本比较高,因为它融入了 RxJS 概念。 要深入理解 HttpClient 最好先掌握 3 个基础技能: XMLHttpRequest -- 看这篇 Fetch -- 看这篇 RxJS -- 看这系列 (如果只是为了

    2024年03月16日
    浏览(53)
  • Angular 17+ 高级教程 – NgModule

    NgModule 在 Angular v14 以前是一门必修课。然而,自 Angular v14 推出 Standalone Component 以后,它的地位变得越来越边缘化了。 本教程从开篇到本篇,所有例子使用的都是 Standalone Component,一点 NgModule 的影子也没有😔。 但是!NgModule 还是有价值的,而且在越复杂的项目中你越可以

    2024年03月12日
    浏览(62)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包