## Angular 父子元件生命週期 在 Angular 中,父子元件的生命周期鉤子(如 `constructor`、`ngOnInit`、`ngAfterContentInit`、`ngAfterViewInit`)的執行順序是有規律的,這些鉤子的調用會按照特定的順序在同一個調用堆疊(call stack)中進行。以下是父子元件的執行順序和關係: ### 建構函數(constructor) 建構函數的執行順序是從父元件到子元件。 1. 父元件建構函數 2. 子元件建構函數 ### `ngOnInit` `ngOnInit` 鉤子的執行順序也是從父元件到子元件。 1. 父元件 `ngOnInit` 2. 子元件 `ngOnInit` ### `ngAfterContentInit` `ngAfterContentInit` 鉤子的執行順序是從父元件到子元件。這是在 Angular 完成內容投影(Content Projection)之後調用的。 1. 父元件 `ngAfterContentInit` 2. 子元件 `ngAfterContentInit` ### `ngAfterViewInit` `ngAfterViewInit` 鉤子的執行順序是從子元件到父元件。這是在 Angular 完成視圖和子視圖的初始化之後調用的。 1. 子元件 `ngAfterViewInit` 2. 父元件 `ngAfterViewInit` ### 執行順序總結 以下是父子元件生命周期鉤子的執行順序: 1. 父元件建構函數 2. 子元件建構函數 3. 父元件 `ngOnInit` 4. 子元件 `ngOnInit` 5. 父元件 `ngAfterContentInit` 6. 子元件 `ngAfterContentInit` 7. 子元件 `ngAfterViewInit` 8. 父元件 `ngAfterViewInit` ### 示例程式碼 以下是一個示例程式碼展示了父子元件的生命周期鉤子的執行順序: #### 父元件 ```typescript import { Component } from '@angular/core'; @Component({ selector: 'app-parent', template: '<app-child></app-child>' }) export class ParentComponent { constructor() { console.log('Parent constructor'); } ngOnInit() { console.log('Parent ngOnInit'); } ngAfterContentInit() { console.log('Parent ngAfterContentInit'); } ngAfterViewInit() { console.log('Parent ngAfterViewInit'); } } ``` #### 子元件 ```typescript import { Component } from '@angular/core'; @Component({ selector: 'app-child', template: '<p>Child Component</p>' }) export class ChildComponent { constructor() { console.log('Child constructor'); } ngOnInit() { console.log('Child ngOnInit'); } ngAfterContentInit() { console.log('Child ngAfterContentInit'); } ngAfterViewInit() { console.log('Child ngAfterViewInit'); } } ``` ### 輸出順序 當應用運行時,控制台將會顯示以下輸出: ``` Parent constructor Child constructor Parent ngOnInit Child ngOnInit Parent ngAfterContentInit Child ngAfterContentInit Child ngAfterViewInit Parent ngAfterViewInit ``` 這個輸出清晰地展示了父子元件在 Angular 生命周期鉤子中的執行順序。所有這些鉤子調用都發生在同一個調用堆疊中,並按順序執行。 在父元件和子元件的生命周期鉤子執行過程中,如果夾雜了父層執行的微任務和宏任務,這些任務的執行順序會受到 JavaScript 事件循環機制的影響。 ## Angular 父子元件生命週期與事件循環執行順序分析 ### JavaScript 事件循環機制 JavaScript 事件循環分為微任務隊列(microtask queue)和宏任務隊列(macrotask queue)。微任務優先於宏任務執行。每次事件循環會依次執行以下步驟: 1. 執行一個宏任務(例如從宏任務隊列中取一個任務並執行)。 2. 執行所有微任務(執行所有在微任務隊列中的任務,直到隊列為空)。 3. 更新渲染。 4. 重複上述步驟。 ### 假設場景 假設在父元件的生命周期鉤子中插入了微任務(如 `Promise` 的 `then` 回調)和宏任務(如 `setTimeout` 回調),下面是執行順序的變化情況。 ### 示例代碼 ```typescript import { Component, OnInit, AfterContentInit, AfterViewInit } from '@angular/core'; @Component({ selector: 'app-parent', template: '<app-child></app-child>' }) export class ParentComponent implements OnInit, AfterContentInit, AfterViewInit { constructor() { console.log('Parent constructor'); } ngOnInit() { console.log('Parent ngOnInit'); Promise.resolve().then(() => { console.log('Parent microtask in ngOnInit'); }); setTimeout(() => { console.log('Parent macrotask in ngOnInit'); }, 0); } ngAfterContentInit() { console.log('Parent ngAfterContentInit'); } ngAfterViewInit() { console.log('Parent ngAfterViewInit'); } } @Component({ selector: 'app-child', template: '<p>Child Component</p>' }) export class ChildComponent implements OnInit, AfterContentInit, AfterViewInit { constructor() { console.log('Child constructor'); } ngOnInit() { console.log('Child ngOnInit'); } ngAfterContentInit() { console.log('Child ngAfterContentInit'); } ngAfterViewInit() { console.log('Child ngAfterViewInit'); } } ``` ### 執行順序分析 根據上述代碼和 JavaScript 事件循環機制,執行順序如下: 1. 父元件構造函數:`Parent constructor` 2. 子元件構造函數:`Child constructor` 3. 父元件 `ngOnInit`:`Parent ngOnInit` 4. 父元件 `ngOnInit` 中的微任務(`Promise` 回調被加入微任務隊列,暫不執行) 5. 父元件 `ngOnInit` 中的宏任務(`setTimeout` 回調被加入宏任務隊列,暫不執行) 6. 子元件 `ngOnInit`:`Child ngOnInit` 7. 父元件 `ngAfterContentInit`:`Parent ngAfterContentInit` 8. 子元件 `ngAfterContentInit`:`Child ngAfterContentInit` 9. 子元件 `ngAfterViewInit`:`Child ngAfterViewInit` 10. 父元件 `ngAfterViewInit`:`Parent ngAfterViewInit` 11. 執行父元件 `ngOnInit` 中的微任務:`Parent microtask in ngOnInit` 12. 執行父元件 `ngOnInit` 中的宏任務:`Parent macrotask in ngOnInit` ### 控制台輸出 ``` Parent constructor Child constructor Parent ngOnInit Child ngOnInit Parent ngAfterContentInit Child ngAfterContentInit Child ngAfterViewInit Parent ngAfterViewInit Parent microtask in ngOnInit Parent macrotask in ngOnInit ``` ### 總結 - **構造函數和生命周期鉤子**:按順序執行,從父元件到子元件。 - **微任務**:在當前宏任務執行完畢後立即執行。即在所有同步代碼和生命周期鉤子執行完畢後,再執行微任務。 - **宏任務**:在所有微任務執行完畢後,再執行宏任務。 理解這個執行順序對於編寫和調試複雜的 Angular 應用非常重要,特別是在處理異步操作時。