Angular 組件生命周期

2022-06-28 13:42 更新

生命周期鉤子

當(dāng) Angular 實(shí)例化組件類并渲染組件視圖及其子視圖時(shí),組件實(shí)例的生命周期就開(kāi)始了。生命周期一直伴隨著變更檢測(cè),Angular 會(huì)檢查數(shù)據(jù)綁定屬性何時(shí)發(fā)生變化,并按需更新視圖和組件實(shí)例。當(dāng) Angular 銷毀組件實(shí)例并從 DOM 中移除它渲染的模板時(shí),生命周期就結(jié)束了。當(dāng) Angular 在執(zhí)行過(guò)程中創(chuàng)建、更新和銷毀實(shí)例時(shí),指令就有了類似的生命周期。

你的應(yīng)用可以使用生命周期鉤子方法來(lái)觸發(fā)組件或指令生命周期中的關(guān)鍵事件,以初始化新實(shí)例,需要時(shí)啟動(dòng)變更檢測(cè),在變更檢測(cè)過(guò)程中響應(yīng)更新,并在刪除實(shí)例之前進(jìn)行清理。

先決條件

在使用生命周期鉤子之前,你應(yīng)該對(duì)這些內(nèi)容有一個(gè)基本的了解:

  • TypeScript
  • Angular 應(yīng)用設(shè)計(jì)基礎(chǔ)

響應(yīng)生命周期事件

可以通過(guò)實(shí)現(xiàn)一個(gè)或多個(gè) Angular ?core ?庫(kù)中定義的生命周期鉤子接口來(lái)響應(yīng)組件或指令生命周期中的事件。這些鉤子讓你有機(jī)會(huì)在適當(dāng)?shù)臅r(shí)候?qū)M件或指令實(shí)例進(jìn)行操作,比如 Angular 創(chuàng)建、更新或銷毀這個(gè)實(shí)例時(shí)。

每個(gè)接口都有唯一的一個(gè)鉤子方法,它們的名字是由接口名再加上 ?ng ?前綴構(gòu)成的。比如,?OnInit ?接口的鉤子方法叫做 ?ngOnInit()?。如果你在組件或指令類中實(shí)現(xiàn)了這個(gè)方法,Angular 就會(huì)在首次檢查完組件或指令的輸入屬性后,緊接著調(diào)用它。

@Directive({selector: '[appPeekABoo]'})
export class PeekABooDirective implements OnInit {
  constructor(private logger: LoggerService) { }

  // implement OnInit's `ngOnInit` method
  ngOnInit() {
    this.logIt('OnInit');
  }

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

你不必實(shí)現(xiàn)所有生命周期鉤子,只要實(shí)現(xiàn)你需要的那些就可以了。

生命周期的順序

當(dāng)你的應(yīng)用通過(guò)調(diào)用構(gòu)造函數(shù)來(lái)實(shí)例化一個(gè)組件或指令時(shí),Angular 就會(huì)調(diào)用那個(gè)在該實(shí)例生命周期的適當(dāng)位置實(shí)現(xiàn)了的那些鉤子方法。

Angular 會(huì)按以下順序執(zhí)行鉤子方法。可以用它來(lái)執(zhí)行以下類型的操作。

鉤子方法

用途

時(shí)機(jī)

ngOnChanges()

當(dāng) Angular 設(shè)置或重新設(shè)置數(shù)據(jù)綁定的輸入屬性時(shí)響應(yīng)。 該方法接受當(dāng)前和上一屬性值的 SimpleChanges 對(duì)象

注意,這發(fā)生的非常頻繁,所以你在這里執(zhí)行的任何操作都會(huì)顯著影響性能。

如果組件綁定過(guò)輸入屬性,那么在 ngOnInit() 之前以及所綁定的一個(gè)或多個(gè)輸入屬性的值發(fā)生變化時(shí)都會(huì)調(diào)用。

注意,如果你的組件沒(méi)有輸入屬性,或者你使用它時(shí)沒(méi)有提供任何輸入屬性,那么框架就不會(huì)調(diào)用 ngOnChanges()。

ngOnInit()

在 Angular 第一次顯示數(shù)據(jù)綁定和設(shè)置指令/組件的輸入屬性之后,初始化指令/組件。

在第一輪 ngOnChanges() 完成之后調(diào)用,只調(diào)用一次。而且即使沒(méi)有調(diào)用過(guò) ngOnChanges(),也仍然會(huì)調(diào)用 ngOnInit()(比如當(dāng)模板中沒(méi)有綁定任何輸入屬性時(shí))。

ngDoCheck()

檢測(cè),并在發(fā)生 Angular 無(wú)法或不愿意自己檢測(cè)的變化時(shí)作出反應(yīng)。 

緊跟在每次執(zhí)行變更檢測(cè)時(shí)的 ngOnChanges() 和 首次執(zhí)行變更檢測(cè)時(shí)的 ngOnInit() 后調(diào)用。

ngAfterContentInit()

當(dāng) Angular 把外部?jī)?nèi)容投影進(jìn)組件視圖或指令所在的視圖之后調(diào)用。

第一次 ngDoCheck() 之后調(diào)用,只調(diào)用一次。

ngAfterContentChecked()

每當(dāng) Angular 檢查完被投影到組件或指令中的內(nèi)容之后調(diào)用。

ngAfterContentInit() 和每次 ngDoCheck() 之后調(diào)用

ngAfterViewInit()

當(dāng) Angular 初始化完組件視圖及其子視圖或包含該指令的視圖之后調(diào)用。

第一次 ngAfterContentChecked() 之后調(diào)用,只調(diào)用一次。

ngAfterViewChecked()

每當(dāng) Angular 做完組件視圖和子視圖或包含該指令的視圖的變更檢測(cè)之后調(diào)用。

ngAfterViewInit() 和每次 ngAfterContentChecked() 之后調(diào)用。

ngOnDestroy()

每當(dāng) Angular 每次銷毀指令/組件之前調(diào)用并清掃。 在這兒反訂閱可觀察對(duì)象和分離事件處理器,以防內(nèi)存泄漏。

在 Angular 銷毀指令或組件之前立即調(diào)用。

生命周期范例

現(xiàn)場(chǎng)演練 / 下載范例通過(guò)在受控于根組件 ?AppComponent ?的一些組件上進(jìn)行的一系列練習(xí),演示了生命周期鉤子的運(yùn)作方式。 每一個(gè)例子中,組件都扮演了組件測(cè)試臺(tái)的角色,以展示出一個(gè)或多個(gè)生命周期鉤子方法。

下表列出了這些練習(xí)及其簡(jiǎn)介。 范例代碼也用來(lái)闡明后續(xù)各節(jié)的一些特定任務(wù)。

組件

說(shuō)明

?Peek-a-boo?

展示每個(gè)生命周期鉤子,每個(gè)鉤子方法都會(huì)在屏幕上顯示一條日志。

?Spy?

展示了如何在自定義指令中使用生命周期鉤子。 SpyDirective 實(shí)現(xiàn)了 ngOnInit() 和 ngOnDestroy() 鉤子,并且使用它們來(lái)觀察和匯報(bào)一個(gè)元素何時(shí)進(jìn)入或離開(kāi)當(dāng)前視圖。

?OnChanges?

演示了每當(dāng)組件的輸入屬性之一發(fā)生變化時(shí),Angular 如何調(diào)用 ngOnChanges() 鉤子。并且演示了如何解釋傳給鉤子方法的 changes 對(duì)象。

?DoCheck?

實(shí)現(xiàn)了一個(gè) ngDoCheck() 方法,通過(guò)它可以自定義變更檢測(cè)邏輯。 監(jiān)視該鉤子把哪些變更記錄到了日志中,觀察 Angular 以什么頻度調(diào)用這個(gè)鉤子。

?AfterView?

顯示 Angular 中的視圖所指的是什么。 演示了 ngAfterViewInit() 和 ngAfterViewChecked() 鉤子。

?AfterContent?

展示如何把外部?jī)?nèi)容投影進(jìn)組件中,以及如何區(qū)分“投影進(jìn)來(lái)的內(nèi)容”和“組件的子視圖”。 演示了 ngAfterContentInit() 和 ngAfterContentChecked() 鉤子。

?Counter?

演示了一個(gè)組件和一個(gè)指令的組合,它們各自有自己的鉤子。

初始化組件或指令

使用 ?ngOnInit()? 方法執(zhí)行以下初始化任務(wù)。

  • 在構(gòu)造函數(shù)外部執(zhí)行復(fù)雜的初始化。組件的構(gòu)造應(yīng)該既便宜又安全。比如,你不應(yīng)該在組件構(gòu)造函數(shù)中獲取數(shù)據(jù)。當(dāng)在測(cè)試中創(chuàng)建組件時(shí)或者決定顯示它之前,你不應(yīng)該擔(dān)心新組件會(huì)嘗試聯(lián)系遠(yuǎn)程服務(wù)器。
  • ?ngOnInit()? 是組件獲取初始數(shù)據(jù)的好地方。

  • 在 Angular 設(shè)置好輸入屬性之后設(shè)置組件。構(gòu)造函數(shù)應(yīng)該只把初始局部變量設(shè)置為簡(jiǎn)單的值。
  • 請(qǐng)記住,只有在構(gòu)造完成之后才會(huì)設(shè)置指令的數(shù)據(jù)綁定輸入屬性。如果要根據(jù)這些屬性對(duì)指令進(jìn)行初始化,請(qǐng)?jiān)谶\(yùn)行 ?ngOnInit()? 時(shí)設(shè)置它們。

?ngOnChanges()? 方法是你能訪問(wèn)這些屬性的第一次機(jī)會(huì)。Angular 會(huì)在調(diào)用 ?ngOnInit()? 之前調(diào)用 ?ngOnChanges()?,而且之后還會(huì)調(diào)用多次。但它只調(diào)用一次 ?ngOnInit()?。

在實(shí)例銷毀時(shí)進(jìn)行清理

把清理邏輯放進(jìn) ?ngOnDestroy()? 中,這個(gè)邏輯就必然會(huì)在 Angular 銷毀該指令之前運(yùn)行。

這里是釋放資源的地方,這些資源不會(huì)自動(dòng)被垃圾回收。如果你不這樣做,就存在內(nèi)存泄漏的風(fēng)險(xiǎn)。

  • 取消訂閱可觀察對(duì)象和 DOM 事件。
  • 停止 interval 計(jì)時(shí)器。
  • 反注冊(cè)該指令在全局或應(yīng)用服務(wù)中注冊(cè)過(guò)的所有回調(diào)。

?ngOnDestroy()? 方法也可以用來(lái)通知應(yīng)用程序的其它部分,該組件即將消失。

一般性例子

下面的例子展示了各個(gè)生命周期事件的調(diào)用順序和相對(duì)頻率,以及如何在組件和指令中單獨(dú)使用或同時(shí)使用這些鉤子。

所有生命周期事件的順序和頻率

為了展示 Angular 如何以預(yù)期的順序調(diào)用鉤子,?PeekABooComponent ?演示了一個(gè)組件中的所有鉤子。

實(shí)際上,你很少會(huì)(幾乎永遠(yuǎn)不會(huì))像這個(gè)演示中一樣實(shí)現(xiàn)所有這些接口。

下列快照反映了用戶單擊 Create... 按鈕,然后單擊 Destroy... 按鈕后的日志狀態(tài)。


日志信息的日志和所規(guī)定的鉤子調(diào)用順序是一致的: ?OnChanges?、?OnInit?、?DoCheck ?(3x)、?AfterContentInit?、?AfterContentChecked ?(3x)、 ?AfterViewInit?、?AfterViewChecked ?(3x)和 ?OnDestroy?

注意,該日志確認(rèn)了在創(chuàng)建期間那些輸入屬性(這里是 ?name ?屬性)沒(méi)有被賦值。 這些輸入屬性要等到 ?onInit()? 中才可用,以便做進(jìn)一步的初始化。

如果用戶點(diǎn)擊Update Hero按鈕,就會(huì)看到另一個(gè) ?OnChanges ?和至少兩組 ?DoCheck?、?AfterContentChecked ?和 ?AfterViewChecked ?鉤子。 注意,這三種鉤子被觸發(fā)了很多次,所以讓它們的邏輯盡可能保持精簡(jiǎn)是非常重要的!

使用指令來(lái)監(jiān)視 DOM

這個(gè) ?Spy ?例子演示了如何在指令和組件中使用鉤子方法。?SpyDirective ?實(shí)現(xiàn)了兩個(gè)鉤子 ?ngOnInit()? 和 ?ngOnDestroy()?,以便發(fā)現(xiàn)被監(jiān)視的元素什么時(shí)候位于當(dāng)前視圖中。

這個(gè)模板將 ?SpyDirective ?應(yīng)用到由父組件 ?SpyComponent ?管理的 ?ngFor ?內(nèi)的 ?<div>? 中。

該例子不執(zhí)行任何初始化或清理工作。它只是通過(guò)記錄指令本身的實(shí)例化時(shí)間和銷毀時(shí)間來(lái)跟蹤元素在視圖中的出現(xiàn)和消失。

像這樣的間諜指令可以深入了解你無(wú)法直接修改的 DOM 對(duì)象。你無(wú)法觸及內(nèi)置 ?<div>? 的實(shí)現(xiàn),也無(wú)法修改第三方組件,但是可以用指令來(lái)監(jiān)視這些元素。

這個(gè)指令定義了 ?ngOnInit()? 和 ?ngOnDestroy()? 鉤子,它通過(guò)一個(gè)注入進(jìn)來(lái)的 ?LoggerService ?把消息記錄到父組件中去。

let nextId = 1;

// Spy on any element to which it is applied.
// Usage: <div appSpy>...</div>
@Directive({selector: '[appSpy]'})
export class SpyDirective implements OnInit, OnDestroy {
  private id = nextId++;

  constructor(private logger: LoggerService) { }

  ngOnInit() {
    this.logger.log(`Spy #${this.id} onInit`);
  }

  ngOnDestroy() {
    this.logger.log(`Spy #${this.id} onDestroy`);
  }
}

你可以把這個(gè)偵探指令寫到任何內(nèi)置元素或組件元素上,以觀察它何時(shí)被初始化和銷毀。 下面是把它附加到用來(lái)重復(fù)顯示英雄數(shù)據(jù)的這個(gè) ?<div>? 上。

<p *ngFor="let hero of heroes" appSpy>
  {{hero}}
</p>

每個(gè)“偵探”的創(chuàng)建和銷毀都可以標(biāo)出英雄所在的那個(gè) ?<div>? 的出現(xiàn)和消失。 添加一個(gè)英雄就會(huì)產(chǎn)生一個(gè)新的英雄 ?<div>?。偵探的 ?ngOnInit()? 記錄下了這個(gè)事件。

Reset 按鈕清除了這個(gè) ?heroes ?列表。 Angular 從 DOM 中移除了所有英雄的 div,并且同時(shí)銷毀了附加在這些 div 上的偵探指令。 偵探的 ?ngOnDestroy()? 方法匯報(bào)了它自己的臨終時(shí)刻。

同時(shí)使用組件和指令的鉤子

在這個(gè)例子中,?CounterComponent ?使用了 ?ngOnChanges()? 方法,以便在每次父組件遞增其輸入屬性 ?counter ?時(shí)記錄一次變更。

這個(gè)例子將前例中的 ?SpyDirective ?用于 ?CounterComponent ?的日志,以便監(jiān)視這些日志條目的創(chuàng)建和銷毀。

使用變更檢測(cè)鉤子

一旦檢測(cè)到該組件或指令的輸入屬性發(fā)生了變化,Angular 就會(huì)調(diào)用它的 ?ngOnChanges()? 方法。 這個(gè) onChanges 范例通過(guò)監(jiān)控 ?OnChanges()? 鉤子演示了這一點(diǎn)。

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

?ngOnChanges()? 方法獲取了一個(gè)對(duì)象,它把每個(gè)發(fā)生變化的屬性名都映射到了一個(gè)?SimpleChange?對(duì)象, 該對(duì)象中有屬性的當(dāng)前值和前一個(gè)值。這個(gè)鉤子會(huì)在這些發(fā)生了變化的屬性上進(jìn)行迭代,并記錄它們。

這個(gè)例子中的 ?OnChangesComponent ?組件有兩個(gè)輸入屬性:?hero ?和 ?power?。

@Input() hero!: Hero;
@Input() power = '';

宿主 ?OnChangesParentComponent ?綁定了它們,就像這樣:

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

下面是此例子中的當(dāng)用戶做出更改時(shí)的操作演示:


日志條目把 power 屬性的變化顯示為字符串。但請(qǐng)注意,?ngOnChanges()? 方法不會(huì)捕獲對(duì) ?hero.name? 更改。這是因?yàn)橹挥挟?dāng)輸入屬性的值發(fā)生變化時(shí),Angular 才會(huì)調(diào)用該鉤子。在這種情況下,?hero ?是輸入屬性,?hero ?屬性的值是對(duì) hero 對(duì)象引用 。當(dāng)它自己的 ?name ?屬性的值發(fā)生變化時(shí),對(duì)象引用并沒(méi)有改變。

響應(yīng)視圖的變更

當(dāng) Angular 在變更檢測(cè)期間遍歷視圖樹(shù)時(shí),需要確保子組件中的某個(gè)變更不會(huì)嘗試更改其父組件中的屬性。因?yàn)閱蜗驍?shù)據(jù)流的工作原理就是這樣的,這樣的更改將無(wú)法正常渲染。

如果你需要做一個(gè)與預(yù)期數(shù)據(jù)流反方向的修改,就必須觸發(fā)一個(gè)新的變更檢測(cè)周期,以允許渲染這種變更。這些例子說(shuō)明了如何安全地做出這些改變。

AfterView 例子展示了 ?AfterViewInit()? 和 ?AfterViewChecked()? 鉤子,Angular 會(huì)在每次創(chuàng)建了組件的子視圖后調(diào)用它們。

下面是一個(gè)子視圖,它用來(lái)把英雄的名字顯示在一個(gè) ?<input>? 中:

@Component({
  selector: 'app-child-view',
  template: `
    <label for="hero-name">Hero name: </label>
    <input type="text" id="hero-name" [(ngModel)]="hero">
  `
})
export class ChildViewComponent {
  hero = 'Magneta';
}

?AfterViewComponent ?把這個(gè)子視圖顯示在它的模板中

template: `
  <div>child view begins</div>
    <app-child-view></app-child-view>
  <div>child view ends</div>
`

下列鉤子基于子視圖中的每一次數(shù)據(jù)變更采取行動(dòng),它只能通過(guò)帶?@ViewChild?裝飾器的屬性來(lái)訪問(wèn)子視圖。

export class AfterViewComponent implements  AfterViewChecked, AfterViewInit {
  private prevHero = '';

  // Query for a VIEW child of type `ChildViewComponent`
  @ViewChild(ChildViewComponent) viewChild!: ChildViewComponent;

  ngAfterViewInit() {
    // viewChild is set after the view has been initialized
    this.logIt('AfterViewInit');
    this.doSomething();
  }

  ngAfterViewChecked() {
    // viewChild is updated after the view has been checked
    if (this.prevHero === this.viewChild.hero) {
      this.logIt('AfterViewChecked (no change)');
    } else {
      this.prevHero = this.viewChild.hero;
      this.logIt('AfterViewChecked');
      this.doSomething();
    }
  }
  // ...
}

在更新視圖之前等待

在這個(gè)例子中,當(dāng)英雄名字超過(guò) 10 個(gè)字符時(shí),?doSomething()? 方法會(huì)更新屏幕,但在更新 ?comment ?之前會(huì)等一個(gè)節(jié)拍(tick)。

// This surrogate for real business logic sets the `comment`
private doSomething() {
  const c = this.viewChild.hero.length > 10 ? "That's a long name" : '';
  if (c !== this.comment) {
    // Wait a tick because the component's view has already been checked
    this.logger.tick_then(() => this.comment = c);
  }
}

在組件的視圖合成完之后,就會(huì)觸發(fā) ?AfterViewInit()? 和 ?AfterViewChecked()? 鉤子。如果你修改了這段代碼,讓這個(gè)鉤子立即修改該組件的數(shù)據(jù)綁定屬性 ?comment?,你就會(huì)發(fā)現(xiàn) Angular 拋出一個(gè)錯(cuò)誤。

?LoggerService.tick_then()? 語(yǔ)句把日志的更新工作推遲了一個(gè)瀏覽器 JavaScript 周期,也就觸發(fā)了一個(gè)新的變更檢測(cè)周期。

編寫精簡(jiǎn)的鉤子方法來(lái)避免性能問(wèn)題

當(dāng)你運(yùn)行 AfterView 范例時(shí),請(qǐng)注意當(dāng)沒(méi)有發(fā)生任何需要注意的變化時(shí),Angular 仍然會(huì)頻繁的調(diào)用 ?AfterViewChecked()?。 要非常小心你放到這些方法中的邏輯或計(jì)算量。


響應(yīng)被投影內(nèi)容的變更

內(nèi)容投影是從組件外部導(dǎo)入 HTML 內(nèi)容,并把它插入在組件模板中指定位置上的一種途徑。 可以在目標(biāo)中通過(guò)查找下列結(jié)構(gòu)來(lái)認(rèn)出內(nèi)容投影。

  • 元素標(biāo)簽中間的 HTML。
  • 組件模板中的 ?<ng-content>? 標(biāo)簽。

AngularJS 的開(kāi)發(fā)者把這種技術(shù)叫做 ?transclusion?。

這個(gè) AfterContent 例子探索了 ?AfterContentInit()? 和 ?AfterContentChecked()? 鉤子。Angular 會(huì)在把外部?jī)?nèi)容投影進(jìn)該組件時(shí)調(diào)用它們。

這次不再通過(guò)模板來(lái)把子視圖包含進(jìn)來(lái),而是改為從 ?AfterContentComponent ?的父組件中導(dǎo)入它。下面是父組件的模板:

`<after-content>
  <app-child></app-child>
</after-content>`

注意,?<app-child>? 標(biāo)簽被包含在 ?<after-content>? 標(biāo)簽中。 永遠(yuǎn)不要在組件標(biāo)簽的內(nèi)部放任何內(nèi)容 —— 除非你想把這些內(nèi)容投影進(jìn)這個(gè)組件中

現(xiàn)在來(lái)看該組件的模板:

template: `
  <div>projected content begins</div>
    <ng-content></ng-content>
  <div>projected content ends</div>
`

?<ng-content>? 標(biāo)簽是外來(lái)內(nèi)容的占位符。 它告訴 Angular 在哪里插入這些外來(lái)內(nèi)容。 在這里,被投影進(jìn)去的內(nèi)容就是來(lái)自父組件的 ?<app-child>? 標(biāo)簽。


使用 AfterContent 鉤子

AfterContent 鉤子和 AfterView 相似。關(guān)鍵的不同點(diǎn)是子組件的類型不同。

  • AfterView 鉤子所關(guān)心的是 ?ViewChildren?,這些子組件的元素標(biāo)簽會(huì)出現(xiàn)在該組件的模板里面。
  • AfterContent 鉤子所關(guān)心的是 ?ContentChildren?,這些子組件被 Angular 投影進(jìn)該組件中。

下列 AfterContent 鉤子基于子級(jí)內(nèi)容中值的變化而采取相應(yīng)的行動(dòng),它只能通過(guò)帶有?@ContentChild?裝飾器的屬性來(lái)查詢到“子級(jí)內(nèi)容”。

export class AfterContentComponent implements AfterContentChecked, AfterContentInit {
  private prevHero = '';
  comment = '';

  // Query for a CONTENT child of type `ChildComponent`
  @ContentChild(ChildComponent) contentChild!: ChildComponent;

  ngAfterContentInit() {
    // contentChild is set after the content has been initialized
    this.logIt('AfterContentInit');
    this.doSomething();
  }

  ngAfterContentChecked() {
    // contentChild is updated after the content has been checked
    if (this.prevHero === this.contentChild.hero) {
      this.logIt('AfterContentChecked (no change)');
    } else {
      this.prevHero = this.contentChild.hero;
      this.logIt('AfterContentChecked');
      this.doSomething();
    }
  }
  // ...
}
不需要等待內(nèi)容更新
該組件的 ?doSomething()? 方法會(huì)立即更新該組件的數(shù)據(jù)綁定屬性 ?comment?。而無(wú)需延遲更新以確保正確渲染 。
Angular 在調(diào)用 AfterView 鉤子之前,就已調(diào)用完所有的 AfterContent 鉤子。 在完成該組件視圖的合成之前, Angular 就已經(jīng)完成了所投影內(nèi)容的合成工作。 ?AfterContent...? 和 ?AfterView...? 鉤子之間有一個(gè)小的時(shí)間窗,允許你修改宿主視圖。

自定義變更檢測(cè)邏輯

要監(jiān)控 ?ngOnChanges()? 無(wú)法捕獲的變更,你可以實(shí)現(xiàn)自己的變更檢查邏輯,比如 DoCheck 的例子。這個(gè)例子展示了你如何使用 ?ngDoCheck()? 鉤子來(lái)檢測(cè)和處理 Angular 自己沒(méi)有捕捉到的變化。

DoCheck 范例使用下面的 ?ngDoCheck()? 鉤子擴(kuò)展了 OnChanges 范例:

ngDoCheck() {

  if (this.hero.name !== this.oldHeroName) {
    this.changeDetected = true;
    this.changeLog.push(`DoCheck: Hero name changed to "${this.hero.name}" from "${this.oldHeroName}"`);
    this.oldHeroName = this.hero.name;
  }

  if (this.power !== this.oldPower) {
    this.changeDetected = true;
    this.changeLog.push(`DoCheck: Power changed to "${this.power}" from "${this.oldPower}"`);
    this.oldPower = this.power;
  }

  if (this.changeDetected) {
      this.noChangeCount = 0;
  } else {
      // log that hook was called when there was no relevant change.
      const count = this.noChangeCount += 1;
      const noChangeMsg = `DoCheck called ${count}x when no change to hero or power`;
      if (count === 1) {
        // add new "no change" message
        this.changeLog.push(noChangeMsg);
      } else {
        // update last "no change" message
        this.changeLog[this.changeLog.length - 1] = noChangeMsg;
      }
  }

  this.changeDetected = false;
}

這段代碼會(huì)檢查某些感興趣的值,捕獲并把它們當(dāng)前的狀態(tài)和之前的進(jìn)行比較。當(dāng) ?hero ?或 ?power ?沒(méi)有實(shí)質(zhì)性變化時(shí),它就會(huì)在日志中寫一條特殊的信息,這樣你就能看到 ?DoCheck()? 被調(diào)用的頻率。其結(jié)果很有啟發(fā)性。


雖然 ?ngDoCheck()? 鉤子可以檢測(cè)出英雄的 ?name ?何時(shí)發(fā)生了變化,但卻非常昂貴。無(wú)論變化發(fā)生在何處,每個(gè)變化檢測(cè)周期都會(huì)以很大的頻率調(diào)用這個(gè)鉤子。在用戶可以執(zhí)行任何操作之前,本例中已經(jīng)調(diào)用了20多次。

這些初始化檢查大部分都是由 Angular 首次在頁(yè)面的其它地方渲染不相關(guān)的數(shù)據(jù)觸發(fā)的。只要把光標(biāo)移動(dòng)到另一個(gè) ?<input>? 就會(huì)觸發(fā)一次調(diào)用。其中的少數(shù)調(diào)用揭示了相關(guān)數(shù)據(jù)的實(shí)際變化情況。如果使用這個(gè)鉤子,那么你的實(shí)現(xiàn)必須非常輕量級(jí),否則會(huì)損害用戶體驗(yàn)。


以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)