## 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 應用非常重要,特別是在處理異步操作時。