react 生命挂钩
为什么我们需要生命周期挂钩? (Why do we need lifecycle hooks?)
Modern front-end frameworks move the application from state to state. Data fuels these updates. These technologies interact with the data which in turn transitions the state. With every state change, there are many specific moments where certain assets become available.
现代的前端框架将应用程序从一个州转移到另一个州。 数据助长了这些更新。 这些技术与数据交互,从而转换状态。 每次状态更改时,都有许多特定时刻可使用某些资产。
At one instance the template might be ready, in another data will have finished uploading. Coding for each instance requires a means of detection. Lifecycle hooks answer this need. Modern front-end frameworks package themselves with a variety of lifecycle hooks. Angular is no exception
在一个实例中,模板可能已准备就绪,而在另一实例中,数据已完成上传。 每个实例的编码都需要一种检测手段。 生命周期挂钩可满足此需求。 现代的前端框架通过各种生命周期挂钩将自身包装在一起。 角度也不例外
生命周期挂钩说明 (Lifecycle Hooks Explained)
Lifecycle hooks are timed methods. They differ in when and why they execute. Change detection triggers these methods. They execute depending on the conditions of the current cycle. Angular runs change detection constantly on its data. Lifecycle hooks help manage its effects.
生命周期挂钩是定时方法。 它们在执行时间和执行方式上有所不同。 更改检测将触发这些方法。 它们根据当前循环的条件执行。 Angular不断对其数据进行更改检测。 生命周期挂钩有助于管理其影响。
An important aspect of these hooks is their order of execution. It never deviates. They execute based on a predictable series of load events produced from a detection cycle. This makes them predictable.
这些挂钩的重要方面是它们的执行顺序。 它永远不会偏离。 它们基于检测周期产生的一系列可预测的负载事件执行。 这使它们可预测。
Some assets are only available after a certain hook executes. Of course, a hook only execute under certain conditions set in the current change detection cycle.
某些资产仅在执行某些挂钩后才可用。 当然,挂钩仅在当前变化检测周期中设置的特定条件下执行。
This article presents the lifecycle hooks in order of their execution (if they all execute). Certain conditions merit a hook’s activation. There are a few who only execute once after component initialization.
本文按生命周期挂钩的执行顺序(如果它们都执行)介绍了生命周期挂钩。 某些条件值得钩子激活。 有些组件在组件初始化后只执行一次。
All lifecycle methods are available from @angular/core
. Although not required, Angular recommends implementing every hook. This practice leads to better error messages regarding the component.
所有生命周期方法都可以从@angular/core
。 尽管不是必需的,但Angular 建议实现每个hook 。 这种做法导致有关该组件更好的错误消息。
生命周期挂钩执行命令 (Order of Lifecycle Hooks' Execution)
ngOnChanges (ngOnChanges)
ngOnChanges
triggers following the modification of @Input
bound class members. Data bound by the @Input()
decorator come from an external source. When the external source alters that data in a detectable manner, it passes through the @Input
property again.
ngOnChanges
在修改@Input
绑定类成员ngOnChanges
触发。 @Input()
装饰器绑定的数据来自外部源。 当外部源以可检测的方式更改该数据时,它将再次通过@Input
属性。
With this update, ngOnChanges
immediately fires. It also fires upon initialization of input data. The hook receives one optional parameter of type SimpleChanges
. This value contains information on the changed input-bound properties.
有了此更新, ngOnChanges
立即触发。 输入数据初始化时也会触发。 挂钩接收一个类型为SimpleChanges
可选参数。 此值包含有关更改的输入绑定属性的信息。
import { Component, Input, OnChanges } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<h3>Child Component</h3>
<p>TICKS: {{ lifecycleTicks }}</p>
<p>DATA: {{ data }}</p>
`
})
export class ChildComponent implements OnChanges {
@Input() data: string;
lifecycleTicks: number = 0;
ngOnChanges() {
this.lifecycleTicks++;
}
}
@Component({
selector: 'app-parent',
template: `
<h1>ngOnChanges Example</h1>
<app-child [data]="arbitraryData"></app-child>
`
})
export class ParentComponent {
arbitraryData: string = 'initial';
constructor() {
setTimeout(() => {
this.arbitraryData = 'final';
}, 5000);
}
}
Summary: ParentComponent binds input data to the ChildComponent. The component receives this data through its @Input
property. ngOnChanges
fires. After five seconds, the setTimeout
callback triggers. ParentComponent mutates the data source of ChildComponent’s input-bound property. The new data flows through the input property. ngOnChanges
fires yet again.
摘要: ParentComponent将输入数据绑定到ChildComponent。 该组件通过其@Input
属性接收此数据。 ngOnChanges
触发。 五秒钟后,将触发setTimeout
回调。 ParentComponent更改ChildComponent的input-bound属性的数据源。 新数据流经input属性。 ngOnChanges
再次触发。
ngOnInit (ngOnInit)
ngOnInit
fires once upon initialization of a component’s input-bound (@Input
) properties. The next example will look similar to the last one. The hook does not fire as ChildComponent receives the input data. Rather, it fires right after the data renders to the ChildComponent template.
初始化组件的输入绑定( @Input
)属性后, ngOnInit
会触发一次。 下一个示例看起来与上一个类似。 当ChildComponent接收输入数据时,该钩子不会触发。 而是在数据渲染到ChildComponent模板后立即触发。
import { Component, Input, OnInit } from '@angular/core';
@Component({
selector: 'app-child',
template: `
<h3>Child Component</h3>
<p>TICKS: {{ lifecycleTicks }}</p>
<p>DATA: {{ data }}</p>
`
})
export class ChildComponent implements OnInit {
@Input() data: string;
lifecycleTicks: number = 0;
ngOnInit() {
this.lifecycleTicks++;
}
}
@Component({
selector: 'app-parent',
template: `
<h1>ngOnInit Example</h1>
<app-child [data]="arbitraryData"></app-child>
`
})
export class ParentComponent {
arbitraryData: string = 'initial';
constructor() {
setTimeout(() => {
this.arbitraryData = 'final';
}, 5000);
}
}
Summary: ParentComponent binds input data to the ChildComponent. ChildComponent receives this data through its @Input
property. The data renders to the template. ngOnInit
fires. After five seconds, the setTimeout
callback triggers. ParentComponent mutates the data source of ChildComponent’s input-bound property. ngOnInit DOES NOT FIRE.
摘要: ParentComponent将输入数据绑定到ChildComponent。 ChildComponent通过其@Input
属性接收此数据。 数据呈现到模板。 ngOnInit
触发。 五秒钟后,将触发setTimeout
回调。 ParentComponent更改ChildComponent的input-bound属性的数据源。 ngOnInit 不会触发。
ngOnInit
is a one-and-done hook. Initialization is its only concern.
ngOnInit
是一个完成的钩子。 初始化是它唯一关心的问题。
ngDoCheck (ngDoCheck)
ngDoCheck
fires with every change detection cycle. Angular runs change detection frequently. Performing any action will cause it to cycle. ngDoCheck
fires with these cycles. Use it with caution. It can create performance issues when implemented incorrectly.
ngDoCheck
在每个更改检测周期触发。 角行程经常检测变化。 执行任何操作都会导致其循环。 ngDoCheck
在这些周期内触发。 请谨慎使用。 如果实施不正确,可能会导致性能问题。
ngDoCheck
lets developers check their data manually. They can trigger a new application date conditionally. In conjunction with ChangeDetectorRef
, developers can create their own checks for change detection.
ngDoCheck
使开发人员可以手动检查其数据。 他们可以有条件地触发新的申请日期。 结合ChangeDetectorRef
,开发人员可以创建自己的检查以进行更改检测。
import { Component, DoCheck, ChangeDetectorRef } from '@angular/core';
@Component({
selector: 'app-example',
template: `
<h1>ngDoCheck Example</h1>
<p>DATA: {{ data[data.length - 1] }}</p>
`
})
export class ExampleComponent implements DoCheck {
lifecycleTicks: number = 0;
oldTheData: string;
data: string[] = ['initial'];
constructor(private changeDetector: ChangeDetectorRef) {
this.changeDetector.detach(); // lets the class perform its own change detection
setTimeout(() => {
this.oldTheData = 'final'; // intentional error
this.data.push('intermediate');
}, 3000);
setTimeout(() => {
this.data.push('final');
this.changeDetector.markForCheck();
}, 6000);
}
ngDoCheck() {
console.log(++this.lifecycleTicks);
if (this.data[this.data.length - 1] !== this.oldTheData) {
this.changeDetector.detectChanges();
}
}
}
Pay attention to the console versus the display. The data progress up to ‘intermediate’ before freezing. Three rounds of change detection occur over this period as indicated in the console. One more round of change detection occurs as ‘final’ gets pushed to the end of this.data
. One last round of change detection then occurs. The evaluation of the if statement determines no updates to the view are necessary.
注意控制台和显示屏。 冻结之前,数据将升级到“中间”状态。 如控制台所示,在此期间进行了三轮变更检测。 随着“最终”被推送到this.data
的末尾,又发生了一轮变更检测。 然后发生最后一轮变更检测。 if语句的评估确定不需要对该视图进行任何更新。
Summary: Class instantiates after two rounds of change detection. Class constructor initiates setTimeout
twice. After three seconds, the first setTimeout
triggers change detection. ngDoCheck
marks the display for an update. Three seconds later, the second setTimeout
triggers change detection. No view updates needed according to the evaluation of ngDoCheck
.
摘要:类在经过两轮变更检测后实例化。 类构造函数将setTimeout
初始化两次。 三秒钟后,第一个setTimeout
触发更改检测。 ngDoCheck
将显示标记为更新。 三秒钟后,第二个setTimeout
触发更改检测。 根据ngDoCheck
的评估,无需更新视图。
警告 (Warning)
Before proceeding, learn the difference between the content DOM and view DOM (DOM stands for Document Object Model).
在继续之前,请了解内容DOM和视图DOM之间的区别(DOM代表文档对象模型)。
The content DOM defines the innerHTML of directive elements. Conversely, the view DOM is a component’s template excluding any template HTML nested within a directive. For a better understanding, refer to this blog post.
内容DOM定义了指令元素的innerHTML。 相反,视图DOM是组件的模板,不包括嵌套在指令中的任何模板HTML。 为了更好的理解,请参阅此博客文章 。
ngAfterContentInit (ngAfterContentInit)
ngAfterContentInit
fires after the component’s content DOM initializes (loads for the first time). Waiting on @ContentChild(ren)
queries is the hook’s primary use-case.
组件的内容DOM初始化(首次加载)后,将触发ngAfterContentInit
。 等待@ContentChild(ren)
查询是该挂钩的主要用例。
@ContentChild(ren)
queries yield element references for the content DOM. As such, they are not available until after the content DOM loads. Hence why ngAfterContentInit
and its counterpart ngAfterContentChecked
are used.
@ContentChild(ren)
查询产生内容DOM的元素引用。 因此,它们只有在内容DOM加载之后才可用。 因此,为什么要使用ngAfterContentInit
及其对应的ngAfterContentChecked
。
import { Component, ContentChild, AfterContentInit, ElementRef, Renderer2 } from '@angular/core';
@Component({
selector: 'app-c',
template: `
<p>I am C.</p>
<p>Hello World!</p>
`
})
export class CComponent { }
@Component({
selector: 'app-b',
template: `
<p>I am B.</p>
<ng-content></ng-content>
`
})
export class BComponent implements AfterContentInit {
@ContentChild("BHeader", { read: ElementRef }) hRef: ElementRef;
@ContentChild(CComponent, { read: ElementRef }) cRef: ElementRef;
constructor(private renderer: Renderer2) { }
ngAfterContentInit() {
this.renderer.setStyle(this.hRef.nativeElement, 'background-color', 'yellow')
this.renderer.setStyle(this.cRef.nativeElement.children.item(0), 'background-color', 'pink');
this.renderer.setStyle(this.cRef.nativeElement.children.item(1), 'background-color', 'red');
}
}
@Component({
selector: 'app-a',
template: `
<h1>ngAfterContentInit Example</h1>
<p>I am A.</p>
<app-b>
<h3 #BHeader>BComponent Content DOM</h3>
<app-c></app-c>
</app-b>
`
})
export class AComponent { }
The @ContentChild
query results are available from ngAfterContentInit
. Renderer2
updates the content DOM of BComponent containing a h3
tag and CComponent. This is a common example of content projection.
@ContentChild
查询结果可从ngAfterContentInit
。 Renderer2
更新包含h3
标签和CComponent的BComponent的内容DOM。 这是内容投影的常见示例。
Summary: Rendering starts with AComponent. For it to finish, AComponent must render BComponent. BComponent projects content nested in its element through the <ng-content></ng-content>
element. CComponent is part of the projected content. The projected content finishes rendering. ngAfterContentInit
fires. BComponent finishes rendering. AComponent finishes rendering. ngAfterContentInit
will not fire again.
摘要:渲染从AComponent开始。 为此,AComponent必须呈现BComponent。 BComponent通过<ng-content></ng-content>
元素投影嵌套在其元素中<ng-content></ng-content>
。 CComponent是计划内容的一部分。 投影内容完成渲染。 ngAfterContentInit
触发。 BComponent完成渲染。 AComponent完成渲染。 ngAfterContentInit
将不会再次触发。
ngAfterContentChecked (ngAfterContentChecked)
ngAfterContentChecked
fires after every cycle of change detection targeting the content DOM. This lets developers facilitate how the content DOM reacts to change detection. ngAfterContentChecked
can fire frequently and cause performance issues if poorly implemented.
ngAfterContentChecked
在针对内容DOM的每个更改检测周期之后触发。 这使开发人员可以简化内容DOM对更改检测的React。 ngAfterContentChecked
如果执行ngAfterContentChecked
可能会经常触发并导致性能问题。
ngAfterContentChecked
fires during a component’s initialization stages too. It comes right after ngAfterContentInit
.
ngAfterContentChecked
在组件的初始化阶段触发。 它ngAfterContentInit
在ngAfterContentInit
之后。
import { Component, ContentChild, AfterContentChecked, ElementRef, Renderer2 } from '@angular/core';
@Component({
selector: 'app-c',
template: `
<p>I am C.</p>
<p>Hello World!</p>
`
})
export class CComponent { }
@Component({
selector: 'app-b',
template: `
<p>I am B.</p>
<button (click)="$event">CLICK</button>
<ng-content></ng-content>
`
})
export class BComponent implements AfterContentChecked {
@ContentChild("BHeader", { read: ElementRef }) hRef: ElementRef;
@ContentChild(CComponent, { read: ElementRef }) cRef: ElementRef;
constructor(private renderer: Renderer2) { }
randomRGB(): string {
return `rgb(${Math.floor(Math.random() * 256)},
${Math.floor(Math.random() * 256)},
${Math.floor(Math.random() * 256)})`;
}
ngAfterContentChecked() {
this.renderer.setStyle(this.hRef.nativeElement, 'background-color', this.randomRGB());
this.renderer.setStyle(this.cRef.nativeElement.children.item(0), 'background-color', this.randomRGB());
this.renderer.setStyle(this.cRef.nativeElement.children.item(1), 'background-color', this.randomRGB());
}
}
@Component({
selector: 'app-a',
template: `
<h1>ngAfterContentChecked Example</h1>
<p>I am A.</p>
<app-b>
<h3 #BHeader>BComponent Content DOM</h3>
<app-c></app-c>
</app-b>
`
})
export class AComponent { }
This hardly differs from ngAfterContentInit
. A mere <button></button>
was added to BComponent. Clicking it causes a change detection loop. This activates the hook as indicated by the randomization of background-color
.
这与ngAfterContentInit
几乎没有区别。 仅将<button></button>
添加到BComponent。 单击它会导致更改检测循环。 如background-color
的随机化所示,这将激活钩子。
Summary: Rendering starts with AComponent. For it to finish, AComponent must render BComponent. BComponent projects content nested in its element through the <ng-content></ng-content>
element. CComponent is part of the projected content. The projected content finishes rendering. ngAfterContentChecked
fires. BComponent finishes rendering. AComponent finishes rendering. ngAfterContentChecked
may fire again through change detection.
摘要:渲染从AComponent开始。 为此,AComponent必须呈现BComponent。 BComponent通过<ng-content></ng-content>
元素投影嵌套在其元素中<ng-content></ng-content>
。 CComponent是计划内容的一部分。 投影内容完成渲染。 ngAfterContentChecked
触发。 BComponent完成渲染。 AComponent完成渲染。 ngAfterContentChecked
可能会通过更改检测再次触发。
ngAfterViewInit (ngAfterViewInit)
ngAfterViewInit
fires once after the view DOM finishes initializing. The view always loads right after the content. ngAfterViewInit
waits on @ViewChild(ren)
queries to resolve. These elements are queried from within the same view of the component.
视图DOM完成初始化后, ngAfterViewInit
会触发一次。 视图始终在内容之后立即加载。 ngAfterViewInit
等待@ViewChild(ren)
查询解决。 从组件的同一视图中查询这些元素。
In the example below, BComponent’s h3
header is queried. ngAfterViewInit
executes as soon as the query’s results are available.
在下面的示例中,查询BComponent的h3
标头。 ngAfterViewInit
查询结果可用, ngAfterViewInit
执行。
import { Component, ViewChild, AfterViewInit, ElementRef, Renderer2 } from '@angular/core';
@Component({
selector: 'app-c',
template: `
<p>I am C.</p>
<p>Hello World!</p>
`
})
export class CComponent { }
@Component({
selector: 'app-b',
template: `
<p #BStatement>I am B.</p>
<ng-content></ng-content>
`
})
export class BComponent implements AfterViewInit {
@ViewChild("BStatement", { read: ElementRef }) pStmt: ElementRef;
constructor(private renderer: Renderer2) { }
ngAfterViewInit() {
this.renderer.setStyle(this.pStmt.nativeElement, 'background-color', 'yellow');
}
}
@Component({
selector: 'app-a',
template: `
<h1>ngAfterViewInit Example</h1>
<p>I am A.</p>
<app-b>
<h3>BComponent Content DOM</h3>
<app-c></app-c>
</app-b>
`
})
export class AComponent { }
Renderer2
changes the background color of BComponent’s header. This indicates the view element was successfully queried thanks to ngAfterViewInit
.
Renderer2
更改BComponent标题的背景颜色。 这表明通过ngAfterViewInit
成功查询view元素。
Summary: Rendering starts with AComponent. For it to finish, AComponent must render BComponent. BComponent projects content nested in its element through the <ng-content></ng-content>
element. CComponent is part of the projected content. The projected content finishes rendering. BComponent finishes rendering. ngAfterViewInit
fires. AComponent finishes rendering. ngAfterViewInit
will not fire again.
摘要:渲染从AComponent开始。 为此,AComponent必须呈现BComponent。 BComponent通过<ng-content></ng-content>
元素投影嵌套在其元素中<ng-content></ng-content>
。 CComponent是计划内容的一部分。 投影内容完成渲染。 BComponent完成渲染。 ngAfterViewInit
触发。 AComponent完成渲染。 ngAfterViewInit
不会再次触发。
ngAfterViewChecked (ngAfterViewChecked)
ngAfterViewChecked
fires after any change detection cycle targeting the component’s view. The ngAfterViewChecked
hook lets developers facilitate how change detection affects the view DOM.
ngAfterViewChecked
在针对组件视图的任何更改检测周期之后触发。 ngAfterViewChecked
挂钩使开发人员可以方便地进行更改检测如何影响视图DOM。
import { Component, ViewChild, AfterViewChecked, ElementRef, Renderer2 } from '@angular/core';
@Component({
selector: 'app-c',
template: `
<p>I am C.</p>
<p>Hello World!</p>
`
})
export class CComponent { }
@Component({
selector: 'app-b',
template: `
<p #BStatement>I am B.</p>
<button (click)="$event">CLICK</button>
<ng-content></ng-content>
`
})
export class BComponent implements AfterViewChecked {
@ViewChild("BStatement", { read: ElementRef }) pStmt: ElementRef;
constructor(private renderer: Renderer2) { }
randomRGB(): string {
return `rgb(${Math.floor(Math.random() * 256)},
${Math.floor(Math.random() * 256)},
${Math.floor(Math.random() * 256)})`;
}
ngAfterViewChecked() {
this.renderer.setStyle(this.pStmt.nativeElement, 'background-color', this.randomRGB());
}
}
@Component({
selector: 'app-a',
template: `
<h1>ngAfterViewChecked Example</h1>
<p>I am A.</p>
<app-b>
<h3>BComponent Content DOM</h3>
<app-c></app-c>
</app-b>
`
})
export class AComponent { }
Summary: Rendering starts with AComponent. For it to finish, AComponent must render BComponent. BComponent projects content nested in its element through the <ng-content></ng-content>
element. CComponent is part of the projected content. The projected content finishes rendering. BComponent finishes rendering. ngAfterViewChecked
fires. AComponent finishes rendering. ngAfterViewChecked
may fire again through change detection.
摘要:渲染从AComponent开始。 为此,AComponent必须呈现BComponent。 BComponent通过<ng-content></ng-content>
元素投影嵌套在其元素中<ng-content></ng-content>
。 CComponent是计划内容的一部分。 投影内容完成渲染。 BComponent完成渲染。 ngAfterViewChecked
触发。 AComponent完成渲染。 ngAfterViewChecked
可能会通过更改检测再次触发。
Clicking the <button></button>
element initiates a round of change detection. ngAfterContentChecked
fires and randomizes the background-color
of the queried elements each button click.
单击<button></button>
元素将启动一轮更改检测。 ngAfterContentChecked
触发并随机化每个按钮单击的查询元素的background-color
。
ngOnDestroy (ngOnDestroy)
ngOnDestroy
fires upon a component’s removal from the view and subsequent DOM. This hook provides a chance to clean up any loose ends before a component’s deletion.
ngOnDestroy
在从视图和后续DOM中删除组件时触发。 该挂钩可在删除组件之前清理所有松动的末端。
import { Directive, Component, OnDestroy } from '@angular/core';
@Directive({
selector: '[appDestroyListener]'
})
export class DestroyListenerDirective implements OnDestroy {
ngOnDestroy() {
console.log("Goodbye World!");
}
}
@Component({
selector: 'app-example',
template: `
<h1>ngOnDestroy Example</h1>
<button (click)="toggleDestroy()">TOGGLE DESTROY</button>
<p appDestroyListener *ngIf="destroy">I can be destroyed!</p>
`
})
export class ExampleComponent {
destroy: boolean = true;
toggleDestroy() {
this.destroy = !this.destroy;
}
}
Summary: The button is clicked. ExampleComponent’s destroy
member toggles false. The structural directive *ngIf
evaluates to false. ngOnDestroy
fires. *ngIf
removes its host <p></p>
. This process repeats any number of times clicking the button to toggle destroy
to false.
摘要:单击该按钮。 ExampleComponent的destroy
成员切换为false。 结构指令*ngIf
计算结果为false。 ngOnDestroy
触发。 *ngIf
删除其主机<p></p>
。 单击该按钮以将destroy
切换为false时,此过程将重复任意次数。
结论 (Conclusion)
Remember that certain conditions must be met for each hook. They will always execute in order of each other regardless. This makes hooks predictable enough to work with even if some do not execute.
请记住,每个挂钩必须满足某些条件。 无论如何,它们将始终按彼此的顺序执行。 即使没有执行钩子,这也足以使钩子可预测地工作。
With lifecycle hooks, timing the execution of a class is easy. They let developers track where change detection is occurring and how the application should react. They stall for code that requires load-based dependencies available only after sometime.
使用生命周期挂钩,计时类的执行很容易。 它们使开发人员可以跟踪发生更改检测的位置以及应用程序应如何应对。 它们停顿一些需要基于负载的依赖项的代码,这些代码仅在一段时间后才可用。
The component lifecycle characterizes modern front end frameworks. Angular lays out its lifecycle by providing the aforementioned hooks.
组件生命周期是现代前端框架的特征。 Angular通过提供上述钩子来规划其生命周期。
资料来源 (Sources)
Angular Team. “Lifecycle Hooks”. Google. Accessed 2 June 2018
Gechev, Minko. “ViewChildren and ContentChildren in Angular”. Accessed 2 June 2018
杰切夫,敏哥。 “ Angular中的ViewChildren和ContentChildren”。 于2018年6月2日访问
资源资源 (Resources )
翻译自: https://www.freecodecamp.org/news/angular-lifecycle-hooks/
react 生命挂钩