angular 父子组件通信时的属性变化生命周期钩子分析

子组件

<div>
    <label for="username">名</label>
    <input id="username" type="text" [(ngModel)]="username" (input)="onUsernameInput($event)">
    <!-- 使用ngModel必须在当前模块的module.ts中的import数组中加入FormsModule-->
    <div>
        <span>姓名</span>
        <span>{{surname}}</span>
        <span>{{username}}</span>
    </div>
</div>
import { Component, Input, Output, EventEmitter, OnInit, SimpleChanges, OnChanges, DoCheck, AfterContentInit, AfterContentChecked } from '@angular/core';

@Component({
  selector: 'app-sub',
  templateUrl: './sub.component.html',
  styleUrls: ['./sub.component.sass']
})
export class SubComponent implements OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked {
  @Input() surname: string;
  @Output() usernameChange = new EventEmitter<string>();
  username: string = '';
  // 属性与方法最先被定义
  constructor() {
    this.log('declaration');
    this.log('constructor');
  }

  log(str) {
    const spaceStr = '                        ';
    console.log(`${spaceStr}${(str + spaceStr + '   ').substr(0, 14)}       sub.surname='${this.surname}'  sub.username='${this.username}'`);
  }

  onUsernameInput($event) {
    console.log('子组件输入框发生了变化' + $event.target.value)
    this.usernameChange.emit($event.target.value);
  }

  // 当 Angular 设置或重新设置数据绑定的输入属性时响应
  // 时机 ngOnInit之前 或 输入属性的值发生变化后 如果没有输入属性 则不调用这个方法
  ngOnChanges(changes: SimpleChanges) {
    const changesRecord = []
    for (const propName in changes) {
      const chng = changes[propName];
      const cur = JSON.stringify(chng.currentValue);
      const prev = JSON.stringify(chng.previousValue);
      changesRecord.push(`输入属性${propName}: ${prev} --> ${cur}`);
    }
    this.log('ngOnChanges'); // 因为组件没有input输入属性 所以不会调用ngOnChanges
  }

  // 在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后 用于初始化指令/组件
  // 时机 在第一轮 ngOnChanges() 完成之后调用
  ngOnInit(): void {
    this.username = 'jin';
    console.log('给username赋值"jin"');
    this.log('ngOnInit')
  }

  // 检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应
  // 时机 ngOnInit或ngOnChanges之后
  ngDoCheck() {
    this.log('ngDoCheck');
  }

  // 当 Angular 把外部内容投影进组件视图或指令所在的视图之后调用
  // 时机 第一次 ngDoCheck() 之后
  ngAfterContentInit() {
    this.log('ngAfterContentInit');
  }

  ngAfterContentChecked() {
    this.log('ngAfterContentChecked')
  }
}


父组件

<div>
    <h1>父组件</h1>
    <label for="surname">姓</label>
    <input id="surname" type="text" [(ngModel)]="surname" (input)="onInput($event)" (blur)="onBlur($event)">
        <div>
            <span>姓名</span>
            <span>{{surname}}</span>
            <span>{{username}}</span>
        </div>
    <h2>子组件</h2>
    <app-sub [surname]="surname" (usernameChange)="onSubUsernameChange($event)"></app-sub>
</div>
import { Component, OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked } from '@angular/core';

@Component({
  selector: 'app-life-cycle',
  templateUrl: './life-cycle.component.html',
  styleUrls: ['./life-cycle.component.sass']
})
export class LifeCycleComponent implements OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked {
  surname: string;
  username: string;
  // 属性与方法最先被定义
  constructor() {
    console.log('父组件\t\t\t\t\t子组件')
    this.log('declaration');
    this.log('constructor');
  }

  log(str) {
    const spaceStr = '                                        ';
    console.log(`${(str + spaceStr).substr(0, 44)}life.surname='${this.surname}' life.username='${this.username}'`);
  }

  onInput($event) {
    console.log('父组件输入框追加输入了' + $event.data)
  }

  onBlur($event) {
    console.log('父组件输入框失去焦点')
  }

  onSubUsernameChange(value: string){
    this.username = value;
  }

  ngOnChanges() {
    this.log('ngOnChanges'); // 因为组件没有input输入属性 所以不会调用ngOnChanges
  }

  // 在 Angular 第一次显示数据绑定和设置指令/组件的输入属性之后 用于初始化指令/组件
  // 时机 在第一轮 ngOnChanges() 完成之后调用
  ngOnInit(): void {
    this.surname = 'h';
    console.log('给surname赋值"x"');
    this.log(`ngOnInit`)
  }

  // 检测,并在发生 Angular 无法或不愿意自己检测的变化时作出反应
  // 时机 ngOnInit或ngOnChanges之后
  ngDoCheck() {
    this.log(`ngDoCheck`);
  }

  // 当 Angular 把外部内容投射进组件视图或指令所在的视图之后调用
  // 时机 第一次 ngDoCheck() 之后
  ngAfterContentInit() {
    this.log('ngAfterContentInit');
  }

  // 每当 Angular 检查完被投射到组件或指令中的内容之后调用
  // ngAfterContentInit() 和每次 ngDoCheck() 之后调用
  ngAfterContentChecked() {
    this.log('ngAfterContentChecked')
  }

  // 当 Angular 初始化完组件视图及其子视图或包含该指令的视图之后调用
  // 第一次 ngAfterContentChecked() 之后调用
  ngAfterViewInit() {
    this.log('ngAfterViewInit')
  }

  // 每当 Angular 做完组件视图和子视图或包含该指令的视图的变更检测之后调用。
  // ngAfterViewInit() 和每次 ngAfterContentChecked() 之后调用
  ngAfterViewChecked() {
    this.log('ngAfterViewChecked')
  }
}

操作:先在父组件输入框追加字符u,然后在子组件输入框追加字符g。现象如下:

分析如下:

页面加载完成后

序号

父组件

子组件

发生的变化

备注
1

declaration

 

life.surname:'undefined' life.username:'undefined'

定义组件类的属性与方法

2

constructor

   
3

 

declaration

sub.surname:'undefined' sub.username:''

 
4 constructor

 

 
5

ngOnInit

 

life.surname:'h'

这里执行了this.surname = 'h'

6

ngDoCheck

  

变更检测

7

ngAfterContentInit

  

把外部内容投射进组件视图或指令所在的视图之后调用

8

ngAfterContentChecked

  

检查完被投射到组件或指令中的内容之后调用。

9 

ngOnChanges

sub.surname:'h'

设置或重新设置数据绑定的输入属性时响应

10 

ngOnInit

sub.username:'jin'

这里执行了this.surname = 'jin'

11 

ngDoCheck

  
12 

ngAfterContentInit

  
13 

ngAfterContentChecked

  
14

ngAfterViewInit

  初始化完组件视图及其子视图或包含该指令的视图之后调用
15

ngAfterViewChecked

  

做完组件视图和子视图或包含该指令的视图的变更检测之后调用

16

ngDoCheck

   
17

ngAfterContentChecked

   
18 

ngDoCheck

  
19 

ngAfterContentChecked

  
20

ngAfterViewChecked

   
操作1 父组件surname输入框增加字符u
序号

父组件

子组件

发生的变化

备注
21

ngDoCheck

 

life.surname:hu

 
22

ngAfterContentChecked

   
23 

ngOnChanges

sub.surname:'hu'

 
24 

ngDoCheck

  
25 

ngAfterContentChecked

  
26

ngAfterViewChecked

   
操作2 父组件surname输入框失去焦点

序号

父组件

子组件

发生的变化

备注
27

ngDoCheck

   
28

ngAfterContentChecked

   
29 

ngDoCheck

  
30 

ngAfterContentChecked

  
31

ngAfterViewChecked

   
操作3 子组件username输入框增加字符g

序号

父组件

子组件

发生的变化

备注
32

ngDoCheck

   
33

ngAfterContentChecked

 

life.username:'jing'

 
34 

ngDoCheck

sub.username:'jing'

 
35 

ngAfterContentChecked

  
36

ngAfterViewChecked

   

操作1234都基本类似

变更检测的顺序:

都是父组件先ngDoCheck ngAfterContentChecked

然后子组件再ngDoCheck ngAfterContentChecked

最后父组件 ngAfterViewChecked

其中的差异是

操作1:

由于子组件的输入属性发生了变化故 在子组件的ngDoCheck之前有个ngOnChanges

操作2和4:

虽然只是失焦事件 angular也会进行一遍检查

操作3:

子组件由于是抛出了事件 然后父组件接收到该事件再去修改的username属性

特别的是虽然是子组件中的username属性先修改 再触发事件去修改父组件中的username属性

但是 父组件的ngDoCheck要比子组件的ngDoCheck更先检测到username属性的变更

这样就保持了上述所说的变更检测的顺序

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值