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

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


一、变化监测

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

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

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


二、ngDoCheck实例

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

例:在子组件添加ngDoCheck方法

// html
<div class="bg">
  <h2>我是子组件</h2>
  <p>问候语:{{greetings}}</p>
  <p>姓名:{{user.name}}</p>
</div>

// 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被调用次数

效果:

在这里插入图片描述


总结:

  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

<div class="bg">
  <h1>变更检测策略</h1>
  <button type="button" (click)="changeStar()">  改变明星属性
  </button>
  <button type="button" (click)="changeStarObject()">
     改变明星对象
  </button>
  <app-movie [title]="title" [star]="star"></app-movie>
</div>

// 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

<div class="bg">
  <h3>{{ title }}</h3>
  <p>
    <label >Star值:</label>
    <span>{{star.firstName}}{{star.lastName}}</span>
  </p>
</div>


// ts 

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

export class MovieComponent implements  {
  @Input() title: string;
  @Input() star;
  
  constructor(
  ) { }

}


效果:

在这里插入图片描述


总结:

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

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

代码:

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

// html

<div class="bg">
  <h3>{{ title }}</h3>
  <p>
    <label >Star值:</label>&nbsp;
    <span>{{star.firstName}}{{star.lastName}}</span>
  </p>
  <p (click)="change()">点击我</p>
</div>


// 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("我点击了")
  }

}

效果:

在这里插入图片描述


总结:

  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("我点击了")
  }

}

效果:

在这里插入图片描述


总结:

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

相关链接:

  1. Angular变化检测的理解(简单清晰又明了)
  2. Angular系列之变化检测
  3. 检测策略
  4. angular变更检测
  5. OnPush实例
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值