06_核心语法:生命周期钩子

核心语法:完整生命周期钩子详解

Angular 组件从创建到销毁会经历一系列生命周期阶段,每个阶段都对应一个钩子方法。这些钩子为开发者提供了在特定时机执行代码的能力,完整掌握它们对于构建高效、可维护的组件至关重要。

生命周期钩子总览

钩子方法 调用时机 调用频率 主要用途
ngOnChanges 输入属性(@Input)初始化或变化时 多次(输入属性变化时) 响应输入属性变化,处理初始值和变化值
ngOnInit 组件初始化完成后(首次 ngOnChanges 后) 一次 执行初始化逻辑(加载数据、订阅事件)
ngDoCheck 每次变更检测运行时 多次(高频) 实现自定义变更检测逻辑
ngAfterContentInit 内容投影(ng-content)完成初始化后 一次 访问投影内容的 DOM 元素
ngAfterContentChecked 内容投影部分完成变更检测后 多次 对投影内容进行后续处理
ngAfterViewInit 组件视图及其子视图渲染完成后 一次 访问组件视图的 DOM 元素
ngAfterViewChecked 组件视图及其子视图完成变更检测后 多次 对视图进行后续处理或调整
ngOnDestroy 组件销毁前 一次 清理资源(取消订阅、移除事件监听)

钩子方法详解与实战

(1)ngOnChanges:响应输入属性变化

当组件的输入属性(通过@Input()装饰器定义)发生变化时触发,是处理输入属性的主要钩子。

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

@Component({

 selector: 'app-product-card',

 standalone: true,

 template: \`

   \<h3>{{ productName }}\</h3>

   \<p>价格:{{ price | currency }}\</p>

   \<p>折扣:{{ discountPercent }}%\</p>

 \`

})

export class ProductCardComponent implements OnChanges {

 @Input() productId?: number;

 @Input() discountPercent = 0;



 productName = '';

 price = 0;



 // 输入属性变化时调用

 ngOnChanges(changes: SimpleChanges) {

   // 检查productId是否有变化

   if (changes\['productId']) {

     const newId = changes\['productId'].currentValue;

     const oldId = changes\['productId'].previousValue;

    

     if (newId !== oldId) {

       this.loadProductData(newId); // 加载新产品数据

     }

   }

  

   // 检查折扣变化

   if (changes\['discountPercent'] && !changes\['discountPercent'].firstChange) {

     console.log(\`折扣从\${changes\['discountPercent'].previousValue}%变为\${this.discountPercent}%\`);

   }

 }



 private loadProductData(productId: number) {

   // 模拟API请求

   setTimeout(() => {

     this.productName = \`商品\${productId}\`;

     this.price = 100 \* productId;

   }, 300);

 }

}

SimpleChanges对象包含输入属性的变化信息,常用属性:

  • currentValue:当前值

  • previousValue:先前值(首次变化时为undefined

  • firstChange:是否为首次变化

(2)ngOnInit:组件初始化

在组件初始化完成后调用,此时所有输入属性已设置完毕,适合执行初始化逻辑。

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

import { HttpClient } from '@angular/common/http';

@Component({

 selector: 'app-user-details',

 standalone: true,

 imports: \[],

 template: \`

   \<p>姓名:{{ user?.name }}\</p>

   \<p>邮箱:{{ user?.email }}\</p>

 \`

})

export class UserDetailsComponent implements OnInit {

 @Input() userId!: number;

 user: any;



 constructor(private http: HttpClient) {}



 ngOnInit() {

   // 初始化时加载用户数据

   this.http.get(\`/api/users/\${this.userId}\`)

     .subscribe(data => {

       this.user = data;

     });

 }

}

注意:避免在构造函数中执行复杂逻辑,构造函数应仅用于依赖注入,初始化逻辑应放在 ngOnInit 中。

(3)ngDoCheck:自定义变更检测

在每次 Angular 变更检测周期中调用,可用于检测 Angular 默认机制无法捕获的变化。

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

@Component({

 selector: 'app-custom-check',

 standalone: true,

 template: \`

   \<p>数组长度:{{ items.length }}\</p>

   \<p>最后修改:{{ lastChange }}\</p>

 \`

})

export class CustomCheckComponent implements DoCheck {

 items: number\[] = \[1, 2, 3];

 lastChange = '从未修改';

 previousLength = 3;



 // 每次变更检测时调用

 ngDoCheck() {

   // 检测数组长度变化

   if (this.items.length !== this.previousLength) {

     this.lastChange = new Date().toLocaleTimeString();

     this.previousLength = this.items.length;

   }

 }



 addItem() {

   this.items.push(Math.random());

 }

}

注意:ngDoCheck 调用频率极高(每次用户交互、定时器触发等都会调用),应避免在其中执行耗时操作。

(4)ngAfterContentInit 与 ngAfterContentChecked:内容投影处理

用于处理通过ng-content投影到组件中的内容:

import { Component, AfterContentInit, AfterContentChecked, ContentChild } from '@angular/core';

import { ElementRef } from '@angular/platform-browser';

@Component({

 selector: 'app-content-container',

 standalone: true,

 template: \`

   \<div class="container">

     \<ng-content select="\[content-header]">\</ng-content>

     \<ng-content select="\[content-body]">\</ng-content>

   \</div>

 \`

})

export class ContentContainerComponent implements AfterContentInit, AfterContentChecked {

 // 获取投影内容中的元素

 @ContentChild('headerRef') headerElement?: ElementRef;



 // 内容投影初始化完成后调用(一次)

 ngAfterContentInit() {

   console.log('投影内容初始化完成');

   this.applyHeaderStyle();

 }



 // 投影内容变更检测完成后调用(多次)

 ngAfterContentChecked() {

   // 每次投影内容变化后重新应用样式

   this.applyHeaderStyle();

 }



 private applyHeaderStyle() {

   if (this.headerElement) {

     this.headerElement.nativeElement.style.color = 'blue';

   }

 }

}

使用该组件时的投影内容:

\<app-content-container>

 \<h2 #headerRef content-header>这是标题\</h2>

 \<p content-body>这是内容\</p>

\</app-content-container>

(5)ngAfterViewInit 与 ngAfterViewChecked:视图处理

用于处理组件自身视图及子组件视图的初始化和变更:

import { Component, AfterViewInit, AfterViewChecked, ViewChild, ElementRef } from '@angular/core';

@Component({

 selector: 'app-chart-container',

 standalone: true,

 template: \`

   \<div #chartContainer class="chart" style="width: 100%; height: 300px;">\</div>

 \`

})

export class ChartContainerComponent implements AfterViewInit, AfterViewChecked {

 @ViewChild('chartContainer') chartContainer?: ElementRef;

 chart?: any;

 containerWidth = 0;



 // 视图初始化完成后调用(一次)

 ngAfterViewInit() {

   if (this.chartContainer) {

     this.containerWidth = this.chartContainer.nativeElement.offsetWidth;

     this.initializeChart(); // 初始化图表

   }

 }



 // 视图变更检测完成后调用(多次)

 ngAfterViewChecked() {

   if (this.chartContainer) {

     const newWidth = this.chartContainer.nativeElement.offsetWidth;

     // 检测容器宽度变化,更新图表

     if (Math.abs(newWidth - this.containerWidth) > 10) {

       this.containerWidth = newWidth;

       this.resizeChart();

     }

   }

 }



 private initializeChart() {

   // 模拟初始化图表库

   this.chart = {

     render: (element: any) => console.log('图表渲染到', element),

     resize: () => console.log('图表已调整大小')

   };

   this.chart.render(this.chartContainer?.nativeElement);

 }



 private resizeChart() {

   this.chart?.resize();

 }

}

注意:在视图钩子中修改数据可能导致 "表达式已检查过" 错误,如需修改可使用

ChangeDetectorRef

detectChanges()

方法。

(6)ngOnDestroy:组件清理

在组件被销毁前调用,用于释放资源,防止内存泄漏:

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

import { interval, Subscription } from 'rxjs';

import { DocumentVisibilityService } from './document-visibility.service';

@Component({

 selector: 'app-resource-intensive',

 standalone: true,

 template: \`\<p>数据点:{{ dataPoints }}\</p>\`

})

export class ResourceIntensiveComponent implements OnInit, OnDestroy {

 dataPoints = 0;

 private dataSubscription?: Subscription;

 private visibilitySubscription?: Subscription;



 constructor(private visibilityService: DocumentVisibilityService) {}



 ngOnInit() {

   // 模拟密集型数据处理

   this.dataSubscription = interval(100).subscribe(() => {

     this.dataPoints++;

   });

  

   // 监听页面可见性变化

   this.visibilitySubscription = this.visibilityService.visibilityChange

     .subscribe(isVisible => {

       console.log('页面可见性变化:', isVisible);

     });

 }



 ngOnDestroy() {

   // 取消订阅,释放资源

   this.dataSubscription?.unsubscribe();

   this.visibilitySubscription?.unsubscribe();

  

   // 移除全局事件监听

   window.removeEventListener('resize', this.handleResize);

  

   console.log('组件已销毁');

 }



 private handleResize = () => {

   // 处理窗口大小变化

 };

}

生命周期钩子执行顺序

单个组件的钩子执行顺序:

  1. 首次渲染:

    ngOnChangesngOnInitngDoCheckngAfterContentInitngAfterContentCheckedngAfterViewInitngAfterViewChecked

  2. 输入属性变化:

    ngOnChangesngDoCheckngAfterContentCheckedngAfterViewChecked

  3. 组件销毁:

    ngOnDestroy

与独立组件和 Signals 的协同

在 Angular v20 的独立组件中,生命周期钩子的行为保持一致,但结合 Signals 时需要注意:

  • 依赖 Signals 的计算应放在computed中,而非ngDoCheck

  • ngOnDestroy中仍需清理手动创建的订阅(如effect中使用的外部资源)

  • 独立组件中无需考虑模块生命周期,钩子仅与组件自身相关

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

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

@Component({

 selector: 'app-signal-lifecycle',

 standalone: true,

 template: \`\<p>计数:{{ count() }}\</p>\`

})

export class SignalLifecycleComponent implements OnDestroy {

 count = signal(0);

 private intervalId?: number;



 constructor() {

   // 使用effect响应Signal变化

   effect(() => {

     console.log('当前计数:', this.count());

   });

  

   this.intervalId = window.setInterval(() => {

     this.count.update(c => c + 1);

   }, 1000);

 }



 ngOnDestroy() {

   // 清理定时器

   if (this.intervalId) {

     clearInterval(this.intervalId);

   }

 }

}

通过完整掌握这些生命周期钩子,你可以精确控制组件在不同阶段的行为,优化资源使用,避免内存泄漏,构建更健壮的 Angular 应用。

posted @ 2025-09-21 18:19  S&L·chuck  阅读(13)  评论(0)    收藏  举报