Angular生命周期解析

每個組件都有一個被Angular管理的生命周期,Angular提供了生命周期鉤子,把這些關(guān)鍵生命時刻暴露出來,賦予我們在它們發(fā)生時采取行動的能力。

鉤子 目的和時機(jī)
ngOnChanges() 當(dāng)Angular(重新)設(shè)置數(shù)據(jù)綁定輸入屬性時響應(yīng)。 該方法接受當(dāng)前和上一屬性值的SimpleChanges對象當(dāng)被綁定的輸入屬性的值發(fā)生變化時調(diào)用,首次調(diào)用一定會發(fā)生在ngOnInit()之前。
ngOnInit() 在Angular第一次顯示數(shù)據(jù)綁定和設(shè)置指令/組件的輸入屬性之后,初始化指令/組件。在第一輪ngOnChanges()完成之后調(diào)用,只調(diào)用一次。
ngDoCheck() 檢測,并在發(fā)生Angular無法或不愿意自己檢測的變化時作出反應(yīng)。在每個Angular變更檢測周期中調(diào)用,ngOnChanges()和ngOnInit()之后.
ngAfterContentInit() 當(dāng)把內(nèi)容投影進(jìn)組件之后調(diào)用。第一次ngDoCheck()之后調(diào)用,只調(diào)用一次。只適用于組件。
ngAfterContentChecked() 每次完成被投影組件內(nèi)容的變更檢測之后調(diào)用,ngAfterContentInit()和每次ngDoCheck()之后調(diào)用只適合組件。
ngAfterViewInit() 每次做完組件視圖和子視圖的變更檢測之后調(diào)用。ngAfterViewInit()和每次ngAfterContentChecked()之后調(diào)用。只適合組件。
ngAfterViewChecked() 每次做完組件視圖和子視圖的變更檢測之后調(diào)用。ngAfterViewInit()和每次ngAfterContentChecked()之后調(diào)用。只適合組件。
ngOnDestroy() 當(dāng)Angular每次銷毀指令/組件之前調(diào)用并清掃。 在這兒反訂閱可觀察對象和分離事件處理器,以防內(nèi)存泄漏。在Angular銷毀指令/組件之前調(diào)用。

下面我們來看代碼

// 子組件
import {AfterContentChecked,AfterContentInit,AfterViewChecked,AfterViewInit,
  DoCheck,  OnChanges, OnDestroy, OnInit,SimpleChange} from '@angular/core';

import { Component, Input } from '@angular/core';
import { LoggerService }    from './logger.service';

let nextId = 1;

export class PeekABoo implements OnInit {
  constructor(private logger: LoggerService) { }

  // 在組件首次加載的時候執(zhí)行,在第一次ngOnchange后
  ngOnInit() { this.logIt(`OnInit`); }

  logIt(msg: string) {
    this.logger.log(`#${nextId++} ${msg}`);
  }
}

@Component({
  selector: 'peek-a-boo',
  template: '<p>Now you see my hero, {{name}}</p>',
  styles: ['p {background: LightYellow; padding: 8px}']
})

export class PeekABooComponent extends PeekABoo implements
             OnChanges, OnInit, DoCheck,
             AfterContentInit, AfterContentChecked,
             AfterViewInit, AfterViewChecked,
             OnDestroy {
  @Input()  name: string;

  private verb = 'initialized';

  constructor(logger: LoggerService) {
    super(logger);

    let is = this.name ? 'is' : 'is not';
    this.logIt(`name ${is} known at construction`);
  }

  // 當(dāng)父組件的變量變化時,自組建的屬性也會改變.
  ngOnChanges(changes: SimpleChanges) {
    let changesMsgs: string[] = [];
    for (let propName in changes) {
      if (propName === 'name') {
        let name = changes['name'].currentValue;
        changesMsgs.push(`name ${this.verb} to "${name}"`);
      } else {
        changesMsgs.push(propName + ' ' + this.verb);
      }
    }
    this.logIt(`OnChanges: ${changesMsgs.join('; ')}`);
    this.verb = 'changed'; // next time it will be a change
  }

  // 在每次需要做出改變的時候的觸發(fā)
  ngDoCheck() { this.logIt(`DoCheck`); }

  // 組件內(nèi)容初始化,只在組件周期發(fā)生一次
  ngAfterContentInit() { this.logIt(`AfterContentInit`);  }

  // 在ngoncheck之后發(fā)生
  ngAfterContentChecked() { this.logIt(`AfterContentChecked`); }

  ngAfterViewInit() { this.logIt(`AfterViewInit`); }

  ngAfterViewChecked() { this.logIt(`AfterViewChecked`); }

  // 組件銷毀
  ngOnDestroy() { this.logIt(`OnDestroy`); }
}

service代碼是輸出打印信息的

import { Injectable } from '@angular/core';

@Injectable()
export class LoggerService {
  logs: string[] = [];
  prevMsg = '';
  prevMsgCount = 1;

  log(msg: string)  {
    if (msg === this.prevMsg) {
      // 若之前的msg和新的msg相同時,count加1.
      this.logs[this.logs.length - 1] = msg + ` (${this.prevMsgCount += 1}x)`;
    } else {
      // New message; log it.
      this.prevMsg = msg;
      this.prevMsgCount = 1;
      this.logs.push(msg);
    }
  }

  clear() { this.logs.length = 0; }

  tick() {  this.tick_then(() => { }); }
  tick_then(fn: () => any) { setTimeout(fn, 0); }
}

我們很奇怪的子組件是如何來使用services的,來看父組件

// 父組件
import { Component } from '@angular/core';
import { LoggerService } from './logger.service';

@Component({
  selector: 'peek-a-boo-parent',
  template: `
  <div class="parent">
    <h2>Peek-A-Boo</h2>
    // 控制子組件的顯影
    <button (click)="toggleChild()">
      {{hasChild ? 'Destroy' : 'Create'}} PeekABooComponent
    </button>
    <button (click)="updateHero()" [hidden]="!hasChild">Update Hero</button>

    <peek-a-boo *ngIf="hasChild" [name]="heroName">
    </peek-a-boo>

    <h4>-- Lifecycle Hook Log --</h4>
    <div *ngFor="let msg of hookLog">{{msg}}</div>
  </div>
  `,
  styles: ['.parent {background: moccasin}'],
  providers:  [ LoggerService ]  //父組件的service可供子組件使用
})
export class PeekABooParentComponent {

  hasChild = false;
  hookLog: string[];

  heroName = 'Windstorm';
  private logger: LoggerService;

  constructor(logger: LoggerService) {
    this.logger = logger;
    this.hookLog = logger.logs;
  }

  toggleChild() {
    this.hasChild = !this.hasChild;
    if (this.hasChild) {
      this.heroName = 'Windstorm';
      this.logger.clear(); // clear log on create
    }
    this.logger.tick();
  }

  updateHero() {
    this.heroName += '!';
    this.logger.tick();
  }
}

下面我們來看一下好玩的東西。。。

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

import { LoggerService } from './logger.service';

let nextId = 1;

// 在這里定義了一個指令,上面有2個鉤子函數(shù)
@Directive({selector: '[mySpy]'})
export class SpyDirective implements OnInit, OnDestroy {
  constructor(private logger: LoggerService) { }

  ngOnInit()    { this.logIt(`onInit`); }

  ngOnDestroy() { this.logIt(`onDestroy`); }

  private logIt(msg: string) {
    this.logger.log(`Spy #${nextId++} ${msg}`);
  }
}

究竟上面的代碼有何用意?

// spy.component.html
<div class="parent">
  <h2>Spy Directive</h2>

  <input [(ngModel)]="newName" (keyup.enter)="addHero()">
  <button (click)="addHero()">Add Hero</button>
  <button (click)="reset()">Reset Heroes</button>

  <p></p>
  //這一步很關(guān)鍵,這些個div具有了生命周期鉤子,從myspy獲得
  <div *ngFor="let hero of heroes" mySpy class="heroes">
    {{hero}}
  </div>
  <h4>-- Spy Lifecycle Hook Log --</h4>
  <div *ngFor="let msg of spyLog">{{msg}}</div>
</div>

@Component({
  selector: 'spy-parent',
  templateUrl: './spy.component.html',
  styles: [
     '.parent {background: khaki;}',
     '.heroes {background: LightYellow; padding: 0 8px}'
  ],
  providers:  [LoggerService]
})
export class SpyParentComponent {
  newName = 'Herbie';
  heroes: string[] = ['Windstorm', 'Magneta'];
  spyLog: string[];

  constructor(private logger: LoggerService) {
    this.spyLog = logger.logs;
  }

  addHero() {
    if (this.newName.trim()) {
      this.heroes.push(this.newName.trim());
      this.newName = '';
      this.logger.tick();
    }
  }
  removeHero(hero: string) {
    this.heroes.splice(this.heroes.indexOf(hero), 1);
    this.logger.tick();
  }
  reset() {
    this.logger.log('-- reset --');
    this.heroes.length = 0;
    this.logger.tick();
  }
}

點(diǎn)擊add時,添加一個英雄,會打印ngoninit

我們接著來看一個很好解釋ngonchange的例子。

// parent.html
<div class="parent">
  <h2>{{title}}</h2>

  <table>
    <tr><td>Power: </td><td><input [(ngModel)]="power"></td></tr>
    <tr><td>Hero.name: </td><td><input [(ngModel)]="hero.name"></td></tr>
  </table>
  <p><button (click)="reset()">Reset Log</button></p>

  <on-changes [hero]="hero.name" [power]="power"></on-changes>
</div>

下面這個代碼很長,仔細(xì)看

import {
  Component, Input, OnChanges,
  SimpleChanges, ViewChild
} from '@angular/core';

class Hero {
  constructor(public name: string) {}
}

@Component({
  selector: 'on-changes',
  template: `
  <div class="hero">
    <p>{{hero}} can {{power}}</p>

    <h4>-- Change Log --</h4>
    <div *ngFor="let chg of changeLog">{{chg}}</div>
  </div>
  `,
  styles: [
    '.hero {background: LightYellow; padding: 8px; margin-top: 8px}',
    'p {background: Yellow; padding: 8px; margin-top: 8px}'
  ]
})
export class OnChangesComponent implements OnChanges {
  @Input() hero: Hero;
  @Input() power: string;

  changeLog: string[] = [];

  ngOnChanges(changes: SimpleChanges) {
    for (let propName in changes) {
      let chng = changes[propName];
      let cur  = JSON.stringify(chng.currentValue);
      let prev = JSON.stringify(chng.previousValue);
      this.changeLog.push(`${propName}: currentValue = ${cur}, previousValue = ${prev}`);
    }
  }

  reset() { this.changeLog.length = 0; }
}

/***************************************/

@Component({
  selector: 'on-changes-parent',
  templateUrl: './on-changes-parent.component.html',
  styles: ['.parent {background: Lavender;}']
})
export class OnChangesParentComponent {
  hero: Hero;
  power: string;
  title = 'OnChanges';
  @ViewChild(OnChangesComponent) childView: OnChangesComponent;

  constructor() {
    this.reset();
  }

  reset() {
    // new Hero object every time; triggers onChanges
    this.hero = new Hero('Windstorm');
    // setting power only triggers onChanges if this value is different
    this.power = 'sing';
    if (this.childView) { this.childView.reset(); }
  }
}

有關(guān)viewChild在以前的章節(jié)已經(jīng)講過,可以回看之前的文章。需要注意的是這種吧2個組件寫在一起的形式。
上面講述了3個生命周期鉤子函數(shù)調(diào)用的例子,還需自己去體味才行。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容