angular知识点--组件生命周期钩子(ngDoCheck & 变化监测)

碎碎念:知识点梳理归纳,如果有什么不对的感谢大家指正一起学习!


ngDoCheck

    • 一、变化监测
    • 二、ngDoCheck实例
    • 三、Angular的两种变化检测策略
      • 3.1 Default策略
      • 3.2 OnPush策略
    • 四、两种策略实例
      • 4.1 在default策略下
      • 4.2在OnPush策略下(1)-- 事件
      • 4.3在OnPush策略下(2)-- 手动监测

一、变化监测

在ngDoCheck钩子之前需要了解一下

  1. 变更检测机制:将组件属性的改变反应到模板上
  2. 异步事件会导致组件数据变化,NgZone会在适当的时机去检验对象的值是否被改动,然后驱动angular的变化监测机制执行
  3. 每个组件都有一个变化监测类的实例,实例提供方法手动管理变化监测。可以给组件做标记,通知angular仅仅监测该组件所在路径上的组件

数据改变了----> NGZone获取了 ----> 通知angular ----> 执行变化检测


二、ngDoCheck实例

  1. 用于变化监测,该钩子方法在每次变化监测发生时被调用
  2. angular变更检测机制由NgZone.实现的。目的:组件属性的变化和页面的变化是同步的
  3. 每一个变化监测周期内,不管数据值是否发生了变化,ngDoCheck都会被调用(例鼠标移动会触发mousemove事件),会被频繁触发调用

例:在子组件添加ngDoCheck方法

// html

我是子组件

问候语:{{greetings}}

姓名:{{user.name}}

// ts export class TestComponent implements OnInit, DoCheck { @Input() greetings: string; @Input() user: { name: string }; noChangeCount: number = 0; oldUserName: string; changeDetected: boolean = false; constructor() {} ngDoCheck(): void { if (this.user.name !== this.oldUserName) { this.changeDetected = true; console.log("DoCheck:user.name从" + this.oldUserName + "变为" + this.user.name); this.oldUserName = this.user.name; } if (this.changeDetected) { this.noChangeCount = 0; } else { this.noChangeCount = this.noChangeCount + 1; console.log("DoCheck:user.name值没发生变化时,doCheck方法已经被调用" + this.noChangeCount + "次"); } this.changeDetected = false; } }

逻辑:

  1. 判断当前user.name值是否发生了改变
  2. 如果发生改变就将变量changeDetected为true(打印)
  3. 然后将当前的用户名赋值给变量oldUserName
  4. 判断如果user.name值没有发生改变,添加doCheck被调用次数

效果:

angular知识点--组件生命周期钩子(ngDoCheck & 变化监测)_第1张图片


总结:

  1. 页面初次加载时也会触发钩子
  2. 仅仅只是鼠标的点击也会频繁触发钩子方法并记录触发次数(并没有改变对象值)
  3. 每次数据的改变会把清空计数器
  4. 不改变name值doCheck方法也会被调用

三、Angular的两种变化检测策略

3.1 Default策略

是Angular默认的变化检测策略,也就是脏检查,只要有值发生变化,就全部从父组件到所有子组件进行检查。

触发条件:

  • 用户输入操作,比如点击,提交等
  • 请求服务端数据(XHR)
  • 定时事件,比如setTimeout,setInterval

优点:
每一次有异步事件发生,Angular都会触发变更检测(脏检查),从根组件开始遍历其子组件,对每一个组件都进行变更检测,对dom进行更新

缺点:
有很多组件状态(state)没有发生变化,无需进行变更检测,进行没有必要的变更检测,如果你的应用程序中组件越多,性能问题会越来越明显

3.2 OnPush策略

  • 就是只有当输入数据(即@Input)的引用发生变化或者有事件触发时,组件才进行变化检测。这种策略检查不彻底,但效率高。

  • 相比Default只需要在@Component装饰器中加上一句:

changeDetection:ChangeDetectionStrategy.OnPush

触发条件:
Input()

优点:
组件的变更检测(脏检查)完全依赖于组件的输入(@Input),只要输入值不变,就不会触发变更检测,也不会对其子组件进行变更检测,在组件很多的时候会有明显的性能提升

缺点:
必须保证输入(@Input)是不可变的,手动进行变化检测


手动变化检测
通过引用变化检测对象ChangeDetectorRef

ChangeDetectorRef介绍

  1. markForCheck() :使用于子组件,将该子组件到根组件之间的路径标记起来,通知angular检测器下次变化检测时一定检查此路径上的组件;(标记为脏的,需要重新渲染)。
  2. detach() :将组件的检测器从检测器树中脱离,不再受检测机制的控制,除非手动调用 reattach() 方法。
  3. reattach() - 脱离的检测器重新链接到检测器树上,使得该组件及其子组件都能执行变化检测
  4. detectChanges() - 从该组件到各个子组件执行一次变化检测 检查该视图及其子视图。与 detach 结合使用可以实现局部变更检测。

四、两种策略实例

4.1 在default策略下

实现逻辑:

  1. 点击“改变明星属性”按钮,可以看到子组件中原本为“周杰伦”被改变成了“吴彦祖”
  2. 点击“改变明星对象”按钮,可以看到子组件中原本为“吴彦祖”被改变成了“刘德华”

代码:

父组件

// html

变更检测策略

// ts @Component({ selector: 'app-prouct-one', templateUrl: './prouct-one.component.html', styleUrls: ['./prouct-one.component.scss'] }) export class ProuctOneComponent implements { title: string = 'default 策略'; star: Star = new Star('周', '杰伦'); changeStar() { this.star.firstName = '吴'; this.star.lastName = '彦祖'; } changeStarObject() { this.star = new Star('刘', '德华'); } } export class Star { constructor( public firstName: string, public lastName: string ) { } }

子组件

// html

{{ title }}

{{star.firstName}}{{star.lastName}}

// ts @Component({ selector: 'app-movie', templateUrl: './movie.component.html', styleUrls: ['./movie.component.scss'], }) export class MovieComponent implements { @Input() title: string; @Input() star; constructor( ) { } }

效果:

angular知识点--组件生命周期钩子(ngDoCheck & 变化监测)_第2张图片


总结:

  1. default策略下,父组件的改变会触发所有的变更检测,子组件的值被改变反映到视图上
  2. 便于区分粉色区是父组件,淡橙色是子组件

4.2在OnPush策略下(1)-- 事件

代码:

父组件的代码不变。下面是子组件的代码

// html

{{ title }}

  {{star.firstName}}{{star.lastName}}

点击我

// ts import { Component, OnInit, Input, DoCheck, ChangeDetectionStrategy, OnChanges, ChangeDetectorRef, SimpleChanges } from '@angular/core'; @Component({ selector: 'app-movie', templateUrl: './movie.component.html', styleUrls: ['./movie.component.scss'], changeDetection: ChangeDetectionStrategy.OnPush }) export class MovieComponent implements OnInit, DoCheck, OnChanges { @Input() title: string; @Input() star; constructor() { } ngOnChanges(changes: SimpleChanges): void { console.log("changes", JSON.stringify(changes, null, 2)) } change() { console.log("我点击了") } }

效果:

angular知识点--组件生命周期钩子(ngDoCheck & 变化监测)_第3张图片


总结:

  1. 点击“改变明星属性”按钮,可以看到子组件中原本为“周杰伦”没有被改变。ngchange中也没有监听到事件。因为,改变的只是对象的值而不是对象的引用 。在该策略下就认为没有被改变,不会进行变化监测。
  2. 点击“改变明星对象”按钮,可以看到子组件中原本为“周杰伦”被改变成了“刘德华”。因为这个改变了对象的引用。
  3. 当触发了“点击我”,发现“周杰伦”,变成了“吴彦祖”。因为在子组件异步的事件会触发变化监测机制,通知anguar来更新数据。
  4. 在这个过程中可以观察onchange的打印,在“改变明星属性”时虽然没有更新到视图也没有触发onchange事件,但是在“改变明星对象”触发后可以看到控制台的打印,"周杰伦"已经被改变为“吴彦祖”。
  5. 截图中,“default策略”忘记改了…这里是“OnPush策略”哈

4.3在OnPush策略下(2)-- 手动监测

代码:

只需要稍微的修改一下子组件的ts代码

// ts

import { Component, OnInit, Input, DoCheck, ChangeDetectionStrategy, OnChanges, ChangeDetectorRef, SimpleChanges } from '@angular/core';

@Component({
  selector: 'app-movie',
  templateUrl: './movie.component.html',
  styleUrls: ['./movie.component.scss'],
  changeDetection: ChangeDetectionStrategy.OnPush
})

export class MovieComponent implements OnInit, DoCheck, OnChanges {
  @Input() title: string;
  @Input() star;
  
  constructor(
    private cdr: ChangeDetectorRef
  ) { }

  ngOnChanges(changes: SimpleChanges): void {
    console.log("changes", JSON.stringify(changes, null, 2))
  }

  ngDoCheck(): void {
    this.cdr.markForCheck();
  }

  change() {
    console.log("我点击了")
  }

}

效果:

angular知识点--组件生命周期钩子(ngDoCheck & 变化监测)_第4张图片


总结:

  1. 在DoCheck钩子中我们执行手动监测的方法,这样不用异步的事件angular也会进行变化监测

相关链接:

  1. Angular变化检测的理解(简单清晰又明了)
  2. Angular系列之变化检测
  3. 检测策略
  4. angular变更检测
  5. OnPush实例

你可能感兴趣的:(angular)