angular学习(变化检测)

angular学习(OnChanges)

1.OnChanges

当组件的任何输入属性发生变化,组件生命周期提供的钩子ngOnChanges 可捕获变化的内容。

示例:

Parent组件是Child组件的父组件,变化检测从根组件开始,会比 Child更早执行变化检测,在执行变化检测时 Parent中的pa属性,会传递到 Child的输入属性param中。此时 Child组件检测到param属性发生变化,因此组件内的 p 元素内的文本值从空字符串 变成 param的值。

child.component.ts

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

@Component({
  selector: 'exe-child',
  // templateUrl: './child.component.html',
  template: `
    <p>{{ param1 }}</p>
    <p>{{ param2 }}</p>
    <button (click)="changeParam1()">change param1</button>
  `,
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit {

  @Input() 
  param1: string;

  @Input()
  param2: string;

  ngOnChanges(changes: SimpleChange){
    console.log(changes);
  }

  //不会触发ngOnChanges钩子,但能改变param1的值
  changeParam1(){
    this.param1 = 'abc';
    console.log(this.param1);
  }

  constructor() { }

  ngOnInit() {
  }
}

parent.component.ts

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

@Component({
  selector: 'exe-parent',
  // templateUrl: './parent.component.html',
  template: `
    <exe-child [param1]="pa1" [param2]="pa2"></exe-child>
  `,
  styleUrls: ['./parent.component.css']
})
export class ParentComponent implements OnInit {

  pa1: string = 'aaa';
  pa2: string = 'bbb';

  constructor() { }

  ngOnInit() {
    // this.pa1 = '666';
  }

}

用ngOnChanges()捕获的变化内容如图:

这里写图片描述

ps:在组件内手动改变输入属性的值,ngOnChanges 钩子不会触发,
点击change param1 只能改变param1的值,不能触发ngOnChanges()。

2.变化检测策略

OnPush策略(当使用OnPush策略的时候,若输入属性没有变化,组件的变化检测将被跳过)
示例:
profile-card.component.ts

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

@Component({
  selector: 'profile-card',
  // templateUrl: './profile-card.component.html',
  template: `
      <profile-name [name]='profile.name'></profile-name>
      <profile-age [age]='profile.age'></profile-age>
      <div>astring:{{astring}}</div>
      <button (click)="changeinchild()" >在子组件改变输入属性</button>
      <button (click)="changeinchildstring()" >在子组件改变输入属性(字符串)</button>
  `,
  styleUrls: ['./profile-card.component.css'],
  changeDetection: ChangeDetectionStrategy.OnPush,//onPush策略
})
export class ProfileCardComponent implements OnInit {

  @Input()
  profile: { name: string, age: number };//可变对象

  @Input()
  astring: string;
  constructor() { 
  }

  ngOnChanges(changes: SimpleChange){
    console.log('触发ngOnChanges');
    console.log(changes);
  }

  ngOnInit() {
  }

  changeinchild() {
    this.profile.name = '在子组件改变输入属性';
    console.log(this.profile);
  }

  changeinchildstring() {
    this.astring = '在子组件改变输入属性(字符串)';
    console.log(this.astring);
  }
}

testfive.component.ts

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

@Component({
  selector: 'ons-page[testfive]',
  // templateUrl: './testfive.component.html',
  template: `
    <ons-page>
      <ons-toolbar>
        <div class="left"><ons-back-button></ons-back-button></div>
      </ons-toolbar>
      <profile-card [profile]='profile' [astring]='astring'></profile-card>
      <br/>
      <button (click)="changeinparent()">在父组件改变输入属性</button>
      <button (click)="changeinparentstring()">在父组件改变输入属性(字符串)</button>
    </ons-page>
  `,
  styleUrls: ['./testfive.component.css']
})
export class TestfiveComponent implements OnInit {

  profile : { name: string, age: number } = {
    name: 'ashin',//输入属性变化
    age: 3//输入属性变化
  };

  astring : string = 'astring';

  constructor() {
  }

  ngOnInit() {

  }

  changeinparent(){
    this.profile.name = "在父组件改变输入属性";
    console.log(this.profile);
  }

  changeinparentstring(){
    this.astring = "在父组件改变输入属性(字符串)";
    console.log(this.astring);
  }
}
3.OnChanges触发时机、用默认策略和OnPush策略的不同
  1. 输入属性是可变对象
- 没用OnPush 用了OnPush
在父组件改变子组件的输入属性 没触发,页面上的值改变【图a】【888】 没触发,页面上的值没改变【图b】【999】
在子组件改变输入属性 没触发,页面上的值改变【图c】【666】 没触发,页面上的值改变【图d】【666】

2. 输入属性是字符串(不可变对象)

- 没用OnPush 用了OnPush
在父组件改变子组件的输入属性 触发,页面上的值改变【图e】【777】 触发,页面上的值改变【图f】【777】
在子组件改变输入属性 没触发,页面上的值改变【图g】【666】 没触发,页面上的值改变【h】【666】

图a:
这里写图片描述
这里写图片描述
图b:
这里写图片描述
图c:
这里写图片描述
图d:
这里写图片描述

图e:
这里写图片描述
这里写图片描述
图f:
这里写图片描述
图g:
这里写图片描述
图h:
这里写图片描述

综上:

在子组件内手动改变输入属性,不会触发ngOnChanges钩子【666】

在父组件内手动改变输入属性时

  1. 输入属性是不可变对象时会触发ngOnChanges钩子【777】
  2. 输入属性是可变对象
    1. 用默认策略时,子组件的输入属性没有发生变化(可变对象内的引用发生变化时才是发生变化,值发生变化不是发生变化),但会从根组件到子组件开始执行变化检测,所以值会在子组件变化检测时改变【888】
    2. 用OnPush策略时,子组件的输入属性没有发生变化,也就不会执行检测,值不会跟着变化【999】

参考:
Angular系列之变化检测(Change Detection)
Angular 2 Change Detection - 2
onChanges钩子使用

4. ChangeDetectorRef

变化检测类,是组件的变化检测器的引用

ChangeDetectorRef 变化检测类的主要方法:

  • markForCheck() - 在组件的 metadata 中如果设置了 changeDetection:ChangeDetectionStrategy.OnPush 条件,那么变化检测不会再次执行,除非手动调用该方法, 该方法的意思是在变化监测时必须检测该组件。
  • detach() - 从变化检测树中分离变化检测器,该组件的变化检测器将不再执行变化检测,除非手动调用 reattach() 方法。
  • reattach() - 重新添加已分离的变化检测器,使得该组件及其子组件都能执行变化检测
  • detectChanges() - 从该组件到各个子组件执行一次变化检测

    1. markForCheck()
import { Component, OnInit, ChangeDetectionStrategy, ChangeDetectorRef, Input, SimpleChange } from '@angular/core';

@Component({
  selector: 'sixchild',
  template: `
    <p>sixchild</p>
    <p>当前值:{{ counter }}</p>
    1.markForCheck()
  `,
  //1. OnPush前,定时器setInterval()内的counter值会同步的视图上
  //2. OnPush时,组件不会执行变化检测,可调用markForCheck()执行检测
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class SixchildComponent implements OnInit {
  counter: number=0 ;

  constructor(private cdRef: ChangeDetectorRef) { }

  ngOnInit() {
    setInterval(()=>{
      this.counter++;
      //执行检测
      this.cdRef.markForCheck();
      console.log(this.counter);
    },1000)
  }
}

这里写图片描述

2.detach()/reattach();

关闭/开启变化检测,和是否用OnPush()无关。

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

@Component({
  selector: 'childsix',
  template: `
    <p>childsix</p>
    <br/>
    <p>当前值:{{ counter }}</p>
    2.detach()/reattach()
    <br/>
    <p>开启/关闭<input type="checkbox" (click)="changetach($event.target.checked)"/></p>

  `,
  // changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ChildsixComponent implements OnInit {
  counter: number=0 ;

  constructor(private cdRef: ChangeDetectorRef) { }

  ngOnInit() {
    setInterval(()=>{
      this.counter++;
      console.log(this.counter);
    },1000)
  }

  //开启/关闭变化检测
  changetach(checked: boolean) {
    if(checked) {
      console.log('开启变化检测');
      this.cdRef.reattach();
    }else {
      console.log('关闭变化检测');
      this.cdRef.detach();
    }
  }
}


  1. detectChanges()

从该组件到各个子组件执行一次变化检测
在父组件testsixComponent中添加OnPush策略,那么子组件也不会有变化检测,子组件sixchild和childsix(子组件内没有添加OnPush)内的setInterval()里更新的数据没有更新到视图。
此时在父组件内调用detectChanges(),则会从该组件到各个子组件执行变化检测(不知道这样理解对不对?)
这里写图片描述

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

@Component({
  selector: 'ons-page[testsix]',
  template: `
    <ons-page>
      <ons-toolbar>
        <div class="left"><ons-back-button></ons-back-button></div> 
        <div class="center">testsix</div>
      </ons-toolbar>
      <sixchild></sixchild>
      <br/>
      <br/>
      <br/>
      <childsix></childsix>
      <br/>
      <br/>
      <br/>
      <p>six:{{six}}</p>
    </ons-page>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TestsixComponent implements OnInit {
  six: number = 0;

  constructor(private cdRef: ChangeDetectorRef) { }

  ngOnInit() {
    setInterval(()=>{
      //从该组件到各个子组件执行一次变化检测
      this.six++;
      this.cdRef.detectChanges();
    },1000)
  }
}

猜你喜欢

转载自blog.csdn.net/kikyou_csdn/article/details/82146851