Angular 2 Directive Lifecycle
在介绍 Angular 2 Directive Lifecycle (生命周期) 之前,我们先来介绍一下 Angular 2 中 Directive (指令) 与 Component (组件) 的关系。
我们再来看一下 Angular 2 中定义的指令和组件接口: // angular2/packages/core/src/metadata/directives.ts export interface Directive { selector?: string; // 用于定义组件在HTML代码中匹配的标签 inputs?: string[]; // 指令的输入属性 outputs?: string[]; // 指令的输出属性 host?: {[key: string]: string}; // 绑定宿主的属性、事件等 providers?: Provider[]; // 设置指令及其子指令可以用的服务 exportAs?: string; // 导出指令,使得可以在模板中调用 queries?: {[key: string]: any}; // 设置指令的查询条件 } export interface Component extends Directive { changeDetection?: ChangeDetectionStrategy; // 指定组件使用的变化检测策略 viewProviders?: Provider[]; // 设置组件及其子组件(不含ContentChildren)可以用的服务 moduleId?: string; // 包含该组件模块的 id,它被用于解析 模版和样式的相对路径 templateUrl?: string; // 为组件指定一个外部模板的URL地址 template?: string; // 为组件指定一个内联的模板 styleUrls?: string[]; // 为组件指定一系列用于该组件的样式表文件 styles?: string[]; // 为组件指定内联样式 animations?: any[]; // 设置组件相关动画 encapsulation?: ViewEncapsulation; // 设置组件视图包装选项 interpolation?: [string,string]; // 设置默认的插值运算符,默认是"{{"和"}}" entryComponents?: Array<Type<any>|any[]>; // 设置需要被提前编译的组件 } 通过观察上图与 Angular 2 中指令与组件的接口定义,我们可以总结出指令与组件之间的关系:组件继承于指令,并扩展了与 UI 视图相关的属性,如 template、styles、animations、encapsulation 等。 下面我们进入正题,开始介绍 Angular 2 指令的生命周期,它是用来记录指令从创建、应用及销毁的过程。Angular 2 提供了一系列与指令生命周期相关的钩子,便于我们监控指令生命周期的变化,并执行相关的操作。Angular 2 中所有的钩子如下图所示:
怎么那么多钩子,是不是被吓到了,没事我们基于指令与组件的区别来分个类:
Angular 2 指令生命周期钩子的作用及调用顺序
Angular 2 指令生命周期钩子详解在详细介绍指令生命周期钩子之前,我们先来介绍一下构造函数: constructor 组件的构造函数会在所有的生命周期钩子之前被调用,它主要用于依赖注入或执行简单的数据初始化操作。 import { Component,ElementRef } from '@angular/core'; @Component({ selector: 'my-app',template: ` <h1>Welcome to Angular World</h1> <p>Hello {{name}}</p> `,}) export class AppComponent { name: string = ''; constructor(public elementRef: ElementRef) { // 使用构造注入的方式注入依赖对象 this.name = 'Semlinker'; // 执行初始化操作 } } ngOnChanges 当数据绑定输入属性的值发生变化的时候,Angular 将会主动调用 ngOnChanges 方法。它会获得一个 SimpleChanges 对象,包含绑定属性的新值和旧值,它主要用于监测组件输入属性的变化。 app.component.ts import { Component } from '@angular/core'; @Component({ selector: 'my-app',template: ` <h4>Welcome to Angular World</h4> <exe-child name="exe-child-component"></exe-child> `,}) export class AppComponent { } child.component.ts import { Component,Input,SimpleChanges,OnChanges } from '@angular/core'; @Component({ selector: 'exe-child',template: ` <p>Child Component</p> <p>{{ name }}</p> ` }) export class ChildComponent implements OnChanges{ @Input() name: string; ngOnChanges(changes: SimpleChanges) { console.dir(changes); } } 以上代码运行后,浏览器的输出结果:
ngOnInit 在第一次 ngOnChanges 执行之后调用,并且只被调用一次。它主要用于执行组件的其它初始化操作或获取组件输入的属性值。 import { Component,OnInit } from '@angular/core'; @Component({ selector: 'exe-child',template: ` <p>父组件的名称:{{pname}} </p> ` }) export class ChildComponent implements OnInit { @Input() pname: string; // 父组件的名称 constructor() { console.log('ChildComponent constructor',this.pname); // Output:undefined } ngOnInit() { console.log('ChildComponent ngOnInit',this.pname); // output: 输入的pname值 } } ngOnDestory 在指令被销毁前,将会调用 ngOnDestory 方法。它主要用于执行一些清理操作,比如:移除事件监听、清除定时器、退订 Observable 等。 @Directive({ selector: '[destroyDirective]' }) export class OnDestroyDirective implements OnDestroy { sayHello: number; constructor() { this.sayHiya = window.setInterval(() => console.log('hello'),1000); } ngOnDestroy() { window.clearInterval(this.sayHiya); } } ngDoCheck 当组件的输入属性发生变化时,将会触发 ngDoCheck 方法。我们可以使用该方法,自定义我们的检测逻辑。它也可以用来加速我们变化检测的速度。 ngAfterContentInit 在组件使用 具体使用示例,请参考 - Angular 2 ContentChild & ContentChildren ngAfterContentChecked 在组件使用 ngAfterViewInit 在组件相应的视图初始化之后调用,它主要用于获取通过 @ViewChild 或 @ViewChildren 属性装饰器查询的视图元素。 具体使用示例,请参考 - Angular 2 ViewChild & ViewChildren ngAfterViewChecked 组件每次检查视图时调用 Angular 2 LifecycleHooks 、SimpleChanges 等相关接口LifecycleHooks 接口 export interface OnChanges { ngOnChanges(changes: SimpleChanges): void; } export interface OnInit { ngOnInit(): void; } export interface DoCheck { ngDoCheck(): void; } export interface OnDestroy { ngOnDestroy(): void; } export interface AfterContentInit { ngAfterContentInit(): void; } export interface AfterContentChecked { ngAfterContentChecked(): void; } export interface AfterViewInit { ngAfterViewInit(): void; } export interface AfterViewChecked { ngAfterViewChecked(): void; } SimpleChange // 用于表示变化对象 export class SimpleChange { constructor(public previousValue: any,public currentValue: any,public firstChange: boolean) {} // 标识是否为首次变化 isFirstChange(): boolean { return this.firstChange; } } SimpleChanges export interface SimpleChanges { [propName: string]: SimpleChange; } Angular 2 View 详解在 Angular 2 中 View (视图) 由三个部分组成:
在 Angular 2 TemplateRef & ViewContainerRef 这篇文章中,我们介绍了 Angular 2 支持的 View(视图) 类型:
接下来我们来分析一下组件对应的 Host Views,具体示例如下: child.component.ts import { Component,OnChanges,AfterViewChecked } from '@angular/core'; @Component({ selector: 'exe-child',template: ` <p>Child Component</p> <p>{{ name }}</p> ` }) export class ChildComponent implements OnChanges,AfterViewChecked{ @Input() name: string; ngOnChanges(changes: SimpleChanges) { console.dir(changes); setTimeout(() => { this.name = 'exe-child-component-1' },0); } ngAfterViewChecked() { console.log('ngAfterViewChecked hook has been called'); } } app.component.ts import { Component } from '@angular/core'; @Component({ selector: 'my-app',}) export class AppComponent { } 以上代码运行后,浏览器的输出结果:
接下来我们来分析一下 ChildComponent 组件,先来看一下编译后的 component.ngfactory.js 文件。 ChildComponent/component.ngfactory.js 代码片段: function View_ChildComponent0(viewUtils,parentView,parentIndex,parentElement) { var self = this; ... self._expr_7 = jit_CD_INIT_VALUE5; } /* * 用于初始化模板内的元素 * ChildComponent - template * <p>Child Component</p> * <p>{{ name }}</p> */ View_ChildComponent0.prototype.createInternal = function(rootSelector) { var self = this; var parentRenderNode = self.renderer.createViewRoot(self.parentElement); ... // (1) 创建p元素 - <p>Child Component</p> self._el_1 = jit_createRenderElement6(self.renderer,parentRenderNode,'p',jit__object_Object_7,self.debug(1,1,6)); // 创建文本元素,设置内容为 - 'Child Component' self._text_2 = self.renderer.createText(self._el_1,'Child Component',self.debug(2,9)); // (2) 创建p元素 - <p>{{ name }}</p> self._el_4 = jit_createRenderElement6(self.renderer,self.debug(4,2,6)); self._text_5 = self.renderer.createText(self._el_4,'',self.debug(5,9)); self.init(null,(self.renderer.directRenderer? null: [...] ),null); return null; }; // 执行变化检测 View_ChildComponent0.prototype.detectChangesInternal = function(throwOnChange) { var self = this; self.debug(5,9); var currVal_7 = jit_inlineInterpolate8(1,self.context.name,''); if (jit_checkBinding9(throwOnChange,self._expr_7,currVal_7)) { self.renderer.setText(self._text_5,currVal_7); self._expr_7 = currVal_7; } }; ChildComponent/wrapper.ngfactory.js 代码片段: function Wrapper_ChildComponent() { var self = this; self._changed = false; self._changes = {}; // 创建Changes对象 self.context = new jit_ChildComponent0(); self._expr_0 = jit_CD_INIT_VALUE1; // {} } Wrapper_ChildComponent.prototype.ngOnDestroy = function() { }; Wrapper_ChildComponent.prototype.check_name =function(currValue,throwOnChange,forceUpdate) { var self = this; // 判断值是否更新,jit_checkBinding2中直接使用looseIdentical(oldValue,newValue) // 进行全等比较(===) if ((forceUpdate || jit_checkBinding2(throwOnChange,self._expr_0,currValue))) { self._changed = true; self.context.name = currValue; // 创建name关联的SimpleChange对象 self._changes['name'] = new jit_SimpleChange3(self._expr_0,currValue); self._expr_0 = currValue; } }; Wrapper_ChildComponent.prototype.ngDoCheck = function(view,el,throwOnChange) { var self = this; var changed = self._changed; self._changed = false; if (!throwOnChange) { if (changed) { self.context.ngOnChanges(self._changes); jit_setBindingDebugInfoForChanges4(view.renderer,self._changes); self._changes = {}; } } return changed; }; ... return Wrapper_ChildComponent }) 我有话说1.注册指令生命周期钩子时,一定要实现对应的接口么 ? 注册指令生命周期钩子时,实现对应的接口不是必须的,接口可以帮助我们在开发阶段尽早地发现错误,因为我们有可能在注册生命周期钩子的时候,写错了某个钩子的名称,在运行时可能不会抛出任何异常,但页面显示却不是预期的效果,因此建议读者还是遵守该开发规范。另外还要注意的一点是,TypeScript 中定义的接口,是不会编译生成 ES5 相关代码,它只用于编译阶段做校验。 未完待续 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |