Angular元件——元件生命週期(一)

starof發表於2018-04-03

元件宣告週期以及angular的變化發現機制 

 

紅色方法只執行一次。

變更檢測執行的綠色方法和和元件初始化階段執行的綠色方法是一個方法。

總共9個方法。

 每個鉤子都是@angular/core庫裡定義的介面。

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

@Component({
  selector: 'app-life',
  templateUrl: './life.component.html',
  styleUrls: ['./life.component.css']
})
export class LifeComponent implements OnInit {

  constructor() { }

  ngOnInit() {
  }

}

雖然介面不是必須的,Angular檢測到鉤子方法就會去執行它,還是建議把介面寫上。

一、鉤子的呼叫順序

import { Component, OnInit, OnChanges, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy, Input, SimpleChange, SimpleChanges } from '@angular/core';

let logIndex: number = 1; //計數器

@Component({
  selector: 'app-life',
  templateUrl: './life.component.html',
  styleUrls: ['./life.component.css']
})
export class LifeComponent implements OnInit, OnChanges, DoCheck, AfterContentInit
  , AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy {
  @Input()
  name: string;

  logIt(msg: string) {
    console.log(`# ${logIndex++}  ${msg}`);
  }
  constructor() {
    this.logIt("name屬性在constructor裡的值是: " + this.name);
  }

  ngOnInit() {
    this.logIt("name屬性在OnInit裡的值是: " + this.name);
  }

  ngOnChanges(changes: SimpleChanges): void { // 傳入一個SimpleChanges物件
    let name = changes['name'].currentValue;
    this.logIt("name屬性在ngOnChanges裡的值是: " + this.name);
  }

  ngDoCheck(): void {
    this.logIt("DoCheck");
  }

  ngAfterContentInit() {
    this.logIt("ngAfterContentInit");
  }

  ngAfterContentChecked() {
    this.logIt("ngAfterContentChecked");
  }

  ngAfterViewInit() {
    this.logIt("ngAfterViewInit");
  }

  ngAfterViewChecked() {
    this.logIt("ngAfterViewChecked");
  }
  ngOnDestroy() {
    this.logIt("ngOnDestory");
  }
}
View Code

初始化邏輯依賴輸入屬性的值時,初始化邏輯一定要寫在ngOnInit裡,不能寫在constructor裡面。

DoCheck在Angular的每個變更檢測週期中呼叫。

ngAfterContentInit和ngAfterContentChecked跟模版,元件的內容投影相關的。

ngAfterViewInit和ngAfterViewChecked跟元件的模版,初始化檢視相關的。

二、onChanges鉤子

父元件初始化或修改子元件的輸入引數時會被呼叫。

需要先理解js中可變物件 和 不可變物件。

//字串是不可變的
var greeting = "Hello";
greeting = "Hello World";
//物件是可變的
var user = { name: "Tom" };
user.name = "Jerry";

例子:

child元件有3個屬性,其中2個是輸入屬性。

 

父元件有一個greeting屬性和一個name屬性是Tom的user物件。

父元件要改變輸入屬性,所以greeting和user.name是雙向繫結。

<div class="parent">
    <h2>我是父元件</h2>
    <div>問候語:<input type="text" [(ngModel)]="greeting"></div>
    <div>
      姓名:
      <input type="text" [(ngModel)]="user.name">
    </div>
    <app-child [greeting]="greeting"  [(user)]="user"> </app-child>
</div>

父元件改變兩個input的值,值變化時候傳入子元件的值也會變化,傳入子元件的輸入屬性的值變化時會觸發ngOnChanges()。

父元件初始化子元件。初始化的時候調一次ngOnChanges(),初始化後子元件的greeting變成Hello,也就是父元件上的greeting的值。

user變成一個name屬性為Tom的物件。

改變輸入屬性的值,父元件問候語greeting改為Helloa。

Angular的變更檢測重新整理不可變物件,也就是greeting的值,然後呼叫ngOnChanges()方法,greeting的值從之前的hello,變為了Helloa。

修改user.name為Tomb,控制檯上沒有列印新的訊息。

因為使用者只是改變了可變物件user的屬性,user物件的引用自身是沒有改變的,所以onChanges()方法沒有被呼叫。

雖然可變物件的屬性改變不會觸發ngOnChanges()方法呼叫,但是子元件的user物件的屬性仍然改變了,由於Angular的變更監測機制仍然捕獲了元件中每個物件的屬性變化。

改變子元件的message屬性也不引起子元件的onChanges()方法呼叫。因為message不是輸入屬性。而ngOnChanges()只有在輸入屬性變化時候被呼叫。

三、變更檢測機制和DoCheck()鉤子

變更檢測由zone.js實現的。保證元件的屬性變化和頁面的變化同步。瀏覽器中發生的非同步事件(點選按鈕,輸入資料,資料從伺服器返回,呼叫了setTimeout()方法)都會觸發變更檢測。

變更檢測執行時,檢測元件模版上的所有繫結關係,如果元件屬性被改變,與其繫結的模版相應區域可能需要更新。

注意:變更檢測機制只是將元件屬性的改變反應到模版上,變更檢測機制本身永遠不會改變元件屬性的值

兩種變更檢測策略。

  • Default   檢測到變化,檢查整個元件樹。
  • OnPush  只有當輸入屬性變化時,才去檢測該元件及其子元件。

Angular應用是一個以主元件為根的元件樹,每個元件都會生成一個變更檢測器,任何一個變更檢測器檢測到變化,zone.js就根據元件的變更檢查策略來檢測元件(也就是調doCheck()鉤子),來判斷元件是否需要更新它的模版。

DoCheck檢查是從根元件開始往下檢查所有的元件樹,不管變更發生在哪個元件。

例子:

監控user.name這種可變物件的屬性的改變。

在child中加一個oldUsername來存變更前的username,加一個changeDetected屬性標誌username是否發生變化,預設是false。 noChangeCount計數器預設是0。

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

@Component({
  selector: 'app-child',
  templateUrl: './child.component.html',
  styleUrls: ['./child.component.css']
})
export class ChildComponent implements OnInit, OnChanges, DoCheck {

  @Input()
  greeting: string;

  @Input()
  user: { name: string };

  message: string = "初始化訊息";
  oldUsername: string;
  changeDetected: boolean = false;
  noChangeCount: number = 0;

  constructor() { }

  ngOnInit() {
  }

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

  ngDoCheck() {
    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) {//變化來計數器清0
      this.noChangeCount = 0;
    } else {//沒變化
      this.noChangeCount++;
      console.log("DoCheck:user.name沒變化時ngDoCheck方法已經被呼叫" + this.noChangeCount + "次")
    }
    this.changeDetected = false;//最後不管變沒變標誌位復位
  }

}
View Code

頁面載入完成:user.name沒變化時DoCheck方法已經被呼叫1次。

滑鼠點選,不改變任何值,點選觸發變更檢測機制,所有元件的DoCheck就會被呼叫。

修改Tom為Tomb,DoCheck捕捉到Tom變為Tomb。

 雖然DoCheck()鉤子可以檢測到user.name什麼時候發生變化,但是使用必須小心,ngDoCheck()鉤子被非常頻繁的呼叫。每次變更檢測週期後發生變化的地方都會呼叫。

對ngDoCheck()的實現必須非常高效,非常輕量級,否則容易引起效能問題。

同理:所有帶Check關鍵字的鉤子方法都要非常小心。 ngDoCheck,ngAfterContentChecked,ngAfterViewChecked.

 

本文作者starof,因知識本身在變化,作者也在不斷學習成長,文章內容也不定時更新,為避免誤導讀者,方便追根溯源,請諸位轉載註明出處:http://www.cnblogs.com/starof/p/8641491.html  有問題歡迎與我討論,共同進步。

相關文章