Angular生命周期实际
生命周期hooks的意义
在Angular中,实现组件的生命周期hook接口办法是应用Angular框架实现插入业务的机会点。
官网文档中生命周期都有哪些内容
在Angular的官网文档中有一个章节Lifecycle hooks来专门解说生命周期的机制与如何来察看组件生命周期。
从官网章节中,咱们能理解到生命周期hook办法 的方方面面并且根本不会有出入。
我列举几点从官网播种到的:
- 在组件中的写法;要实现
OnInit
办法,在组件中增加ngOnInit
办法, 留神多了俩个字母, 每个hook办法均是如此。 - 生命周期事件程序;
- 如何去察看生命周期事件;
- 应用ngOnInit作为业务插入点,而不应该在constructor中放入业务代码;
- ngOnDestroy的执行机会
- 全副生命周期事件触发程序
- ngOnChanges的执行机制
- ...等等
这基本上笼罩到了有组件生命周期的方方面面;
本文重点内容
官网章节内容是基于生命周期自身去讲的,而理论应用过程生命周期的执行过程会与依赖注入机会, 数据流传递,变化检测,父子组件数据变更,组件继承等等诸多个性联合应用,在联合后,执行程序是怎么的对咱们来说有点不那么容易弄明确,只有通过大量思考以及实际后能力得悉并总结出其法则。
与官网的切入点不同,本文心愿从实际中来总结法则。
而本文选取了如下几种场景,进行实际演示,并尝试得出法则:
- 根本的生命周期介绍
- 数据流传递的机会是在哪个具体的事件执行。
- 父子组件中父子组件的生命周期是如何执行的。
- 继承组件中生命周期又是如何执行的。
- 模板中绑定的变量在获取值时与这些生命周期有没有关系?
心愿通过本文的浏览,能帮你拨开一些面纱,让你可能随我一起进一步把握Angular的个性,去思考Angular中生命周期设定去窥探Angular的运行机制;了解Angular的思路、思维以及其为开发者塑造的思考模式。
只有咱们可能依照Angular的形式思考,当遇到简单的业务参加了简单的数据流,简单的组件关系,简单的类关系后,能通过思考疾速理解常识盲区,疾速组织获取相干常识的关键词,这样茫茫Angular的概念以及新常识的陆地里你就变得蛟龙得水,也会成为咱们开发业务代码时,优化重构,解决问题时最尖锐的矛。
lifecycle-hook-basic
因为官网文档中曾经把生命周期的程序介绍并演示了,咱们这里就做个简略的验证和总结,当做一次温习。
首先来看一个理论执行效果图
留神:有认真的小伙伴可能会留神到这个图中,没有看到ngOnChanges事件。不是忘了 根组件不会触发ngOnChanges事件,因为跟组件没有@Input变量;
源码如下
@Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'],})export class AppComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy { name = 'Angular ' + VERSION.major; messages = []; subject = window['subject']; constructor() { this.subject.subscribe((item) => { this.messages.push(item); }); this.subject.next({ type: 'constructor exec', content: 'AppComponent class instance' }); } ngOnChanges() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngOnChanges' }); } ngOnInit() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngOnInit' }); } ngDoCheck() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngDoCheck' }); } ngAfterContentInit() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterContentInit' }); } ngAfterContentChecked() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterContentChecked' }); } ngAfterViewInit() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterViewInit' }); } ngAfterViewChecked() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterViewChecked' }); } ngOnDestroy() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngOnDestroy' }); }}
下图是失常组件,并带有@Input属性
源码如下
@Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'],})export class AppComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy { name = 'Angular ' + VERSION.major; messages = []; subject = window['subject']; constructor() { this.subject.subscribe((item) => { this.messages.push(item); }); this.subject.next({ type: 'constructor exec', content: 'AppComponent class instance' }); }}// HelloComponent 是AppComponent的子视图组件...@Component({ selector: 'hello', template: `<h1>Hi, {{name}}!</h1>`, styles: [`h1 { font-family: Lato; }`],})export class HelloComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy { _name: string = ''; subject = window['subject']; @Input() set name(n: string) { this.subject.next({ type: '@input', content: 'set name update' }); this._name = n; } get name() { // this.subject.next({ type: 'template binding variable get', content: 'get name update' }); 仅演示调用 return this._name; } messages = []; constructor() { this.subject.next({ type: 'constructor exec', content: 'class instance, 拜访@input属性name=' + this.name }); } ngOnChanges() { this.subject.next({ type: 'lifecycle', content: 'ngOnChanges, 拜访@input属性name=' + this.name }); } ngOnInit() { this.subject.next({ type: 'lifecycle', content: 'ngOnInit, 拜访@input属性name=' + this.name }); } ngDoCheck() { this.subject.next({ type: 'lifecycle', content: 'ngDoCheck, 拜访@input属性name=' + this.name }); } ngAfterContentInit() { this.subject.next({ type: 'lifecycle', content: 'ngAfterContentInit, 拜访@input属性name=' + this.name }); } ngAfterContentChecked() { this.subject.next({ type: 'lifecycle', content: 'ngAfterContentChecked, 拜访@input属性name=' + this.name }); } ngAfterViewInit() { this.subject.next({ type: 'lifecycle', content: 'ngAfterViewInit, 拜访@input属性name=' + this.name }); } ngAfterViewChecked() { this.subject.next({ ype: 'lifecycle', content: 'ngAfterViewChecked, 拜访@input属性name=' + this.name }); } ngOnDestroy() { this.subject.next({ type: 'lifecycle', content: 'ngOnDestroy, 拜访@input属性name=' + this.name }); }}
解释并演绎
咱们联合下面程序能够先粗略画一个图如下:
解释下这个图中的每个钩子的含意:
留神:
- 浅灰色名字的事件,在组件的生命周期中只会触发一次,而绿色的随着相应的逻辑变动会屡次触发。
- 这里我将组件构造函数的执行也退出到了察看序列中,因为在业务中,常常会有小伙伴会在constructor中插入业务代码。
所有的办法执行程序如下:
Construction, OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy
Construction
构造函数执行时, 执行一次。
OnChanges
当指令(组件的实现继承了指令)的任何一个可绑定属性发生变化时调用。
要点:
- 从下面根组件的执行成果来看,这个hook不肯定会被调用,只有@Input属性变动才会触发;
- @Input属性的变更次数是没有要求的,所以它的回调次数也是没有限度的。
- 这块要特地留神父级扭转传递值的状况,会导致ngOnChange在生命周期的任何时刻都可能被再次调用。
- 参数是一个输出属性的变更处理器,会蕴含所有变更的输出属性的旧值和新值;
- 如果至多产生了一次变更,则该回调办法会在默认的变更检测器查看完可绑定属性之后、视图子节点和内容子节点查看完之前调用。
- 属性的setter可能代替在这个钩子中执行逻辑。
OnInit
在 Angular 初始化完了该指令的所有数据绑定属性之后调用;
要点:
- 在默认的变更检测器首次查看完该指令的所有数据绑定属性之后,任何子视图或投影内容查看完之前。
- 它会且只会在指令初始化时调用一次
- 定义
ngOnInit()
办法能够解决所有附加的初始化工作。
DoCheck
在变更检测期间,默认的变更检测算法会依据援用来比拟可绑定属性,以查找差别。 你能够应用此钩子来用其余形式检查和响应变更。
要点:
- 除了应用默认的变更查看器执行查看之外,还会为指令执行自定义的变更检测函数
- 默认变更检测器查看更改时,他会触发OnChanges()的执行(如果有),而不在乎你是否进行了额定的变更检测。
- 不应该同时应用DoCheck和OnChanges来响应在同一个输出上产生的更改。
- 默认的变更检测器执行之后调用,并进行变更检测。
- 参见
KeyValueDiffers
和IterableDiffers
,以实现针对汇合对象的自定义变更检测逻辑。 - 在DoCheck中你能够实现监控那些OnChanges无奈捕捉的变更,检测的逻辑须要自行实现。
- 因为DoCheck能够监控出特定变量的何时产生了变动,但这却十分低廉。Angular 在页面的其它中央渲染不相干的数据也会触发这个钩子,所以你的实现必须自行保障用户体验。
AfterContentInit
它会在 Angular 初始化完该指令的所有内容之后立刻调用。
要点:
- 在指令初始化实现之后,它只会调用一次。
- 能够用来解决一些初始化工作
AfterContentChecked
在默认的变更检测器对该指令下的所有内容实现了变更检测之后立刻调用。
AfterViewInit
- 在 Angular 齐全初始化了组件的视图后调用。 定义一个
ngAfterViewInit()
办法来解决一些额定的初始化工作。
AfterViewChecked
在默认的变更检测器对组件视图实现了一轮变更检测周期之后立刻调用。
OnDestroy
在指令、管道或服务被销毁时调用。 用于在实例被销毁时,执行一些自定义清理代码。
进一步阐明:
因为咱们关怀的是什么时候应用这些钩子,大家回到下面这些回调钩子定义处,仔细观察带有Init的钩子内容,能够看到OnInit , AfterContentInit, AfterViewInit 这三个,他们都只执行一次,都能够做一些初始化工作,这三个钩子的区别,就像定义中的形容都是有差异的。
大家有须要能够在文章上面留言,看理论状况是否须要认真解释下,这三个钩子所实用的差异化场景。
没有认真钻研过这三者不同的小伙伴,经常对于应该把本人要实现的异步初始化业务放到哪个钩子中昏头昏脑,所以轻易选一个,要么放在OnInit, 要么AfterViewInit,如果发现放到一个里不行,就换另一个,直到问题解决或消耗很长时间问题解决不了或者留下偶现的bug(之所以偶现是因为没有从技术上保障异步的程序性执行),再次排查也相当吃力。
所以这三个Init十分值得写异步业务比拟多的小伙伴关注。
从第一个场景下,咱们回顾了每一个生命周期钩子都有哪些内容。
接下来咱们看一下带有@Input的场景:
lifecycle-hook&Input
源码如下
//AppComponent html<h1>Hi, Angular 13!</h1><h3>- 演示生命周期钩子函数调用程序<br /></h3><p>Start editing to see some magic happen :)</p><ul> <li *ngFor="let message of messages"> <span class="message-type">{{ message.type }}</span> => <span class="message-content">{{ message.content }}</span> </li></ul><hello [name]="name"></hello>
// AppComponent@Component({ selector: 'my-app', templateUrl: './app.component.html', styleUrls: ['./app.component.css'],})export class AppComponent{ name = 'Angular ' + VERSION.major; messages = []; subject = window['subject']; constructor() { this.subject.subscribe((item) => { this.messages.push(item); }); this.subject.next({ type: 'constructor exec', content: 'AppComponent class instance, 拜访@input属性name=' + this.name }); }}// HelloComponent 是AppComponent的子视图组件...@Component({ selector: 'hello', template: `<h1>Hi, {{name}}!</h1>`, styles: [`h1 { font-family: Lato; }`],})export class HelloComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy { _name: string = ''; subject = window['subject']; @Input() set name(n: string) { this.subject.next({ type: '@input', content: 'set name update' }); this._name = n; } get name() { // this.subject.next({ type: 'template binding variable get', content: 'get name update' }); 仅演示调用 return this._name; } messages = []; constructor() { this.subject.next({ type: 'constructor exec', content: 'class instance, 拜访@input属性name=' + this.name }); } ngOnChanges() { this.subject.next({ type: 'lifecycle', content: 'ngOnChanges, 拜访@input属性name=' + this.name }); } ngOnInit() { this.subject.next({ type: 'lifecycle', content: 'ngOnInit, 拜访@input属性name=' + this.name }); } ngDoCheck() { this.subject.next({ type: 'lifecycle', content: 'ngDoCheck, 拜访@input属性name=' + this.name }); } ngAfterContentInit() { this.subject.next({ type: 'lifecycle', content: 'ngAfterContentInit, 拜访@input属性name=' + this.name }); } ngAfterContentChecked() { this.subject.next({ type: 'lifecycle', content: 'ngAfterContentChecked, 拜访@input属性name=' + this.name }); } ngAfterViewInit() { this.subject.next({ type: 'lifecycle', content: 'ngAfterViewInit, 拜访@input属性name=' + this.name }); } ngAfterViewChecked() { this.subject.next({ ype: 'lifecycle', content: 'ngAfterViewChecked, 拜访@input属性name=' + this.name }); } ngOnDestroy() { this.subject.next({ type: 'lifecycle', content: 'ngOnDestroy, 拜访@input属性name=' + this.name }); }}
执行后果
解释并演绎
咱们给Hello组件增加了输出属性,父组件AppComponent初始化时给name赋值了初始值,仅在angular的解决下输出属性的绑定是产生在Hello组件初始化之前(当然在Hello组件生命周期调用的过程中,父组件随时可能扭转name)。
- 留神在第一次OnChanges触发之后,也就是传递变量的初始值给完后,有些状况咱们会通过逻辑在父组件中调整传递变量的值,这时就会立刻再次触发OnChanges的回调,并且这个回调与HelloComponent组件的OnInit,AfterContentInit等的回调是按工夫程序顺次调用的。也就是OnChanges的触发与AfterContentInit, AfterViewInit是否曾经实现一次执行无关。
咱们在刚刚的AppComponent组件中也退出生命周期的执行,后果会怎么样呢?
lifecycle-hook&child&parent&Input
改变源码如下
export class AppComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy { name = 'Angular ' + VERSION.major; messages = []; subject = window['subject']; constructor() { this.subject.subscribe((item) => { this.messages.push(item); }); this.subject.next({ type: 'constructor exec', content: 'AppComponent class instance, 拜访@input属性name=' + this.name }); } ngOnChanges() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngOnChanges' }); } ngOnInit() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngOnInit' }); } ngDoCheck() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngDoCheck' }); } ngAfterContentInit() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterContentInit' }); } ngAfterContentChecked() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterContentChecked' }); } ngAfterViewInit() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterViewInit' }); } ngAfterViewChecked() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngAfterViewChecked' }); } ngOnDestroy() { this.subject.next({ type: 'lifecycle', content: 'AppComponent ngOnDestroy' }); }}
执行后果
解释并演绎
景象:
- 父组件AppComponent的生命周期回调被子组件分为了俩局部。
- constuctor的执行程序是先父组件,接下来是子组件,接下来才是生命周期的其余函数回调。
- 父组件的ngOnChanges(如果有,第一次), ngOnInit,ngDoCheck,ngAfterContentInit,ngAfterContentChecked会执行较早。
- 父组件name值传递到子组件,触发子组件的OnChanges。
- 子组件的生命周期执行,接下来父组件的ngAfterViewInit, ngAfterViewChecked执行。
要点:
父组件将绑定值传入到子组件是在父组件的生命周期执行到ngAfterContentChecked时触发,这一点很重要
- 意味着,如果在子组件的生命周期(比方:OnInit)中有解决依赖传递变量的逻辑,那么可能得不到最新的传递值。(因为这一点,小伙伴常常陷入困惑中,这也与不理解Init钩子的实用场景无关)
- 父子组件中,AfterViewInit会等到所有的子组件的生命周期执行实现才执行,(这一点个性应该被充分发挥并利用)。
接下来咱们看看存在继承组件的场景下,Angular会怎么解决生命周期回调。
life-hook&child&parent&inheritComponent&input
改变源码如下:
...// 这一次咱们增加了一个BaseComponent作为Hello组件的基类,在Angular中是以Directive来装璜的// 应用Directive的益处// Angular组件继承不会继承元数据,能够应用directive装璜器元数据可配置空来防止配置多余的元数据// Directive是Component装璜器的基类,根本无缝替换@Directive()export class BaseComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy { subject = window['subject']; constructor() { this.subject.next({ type: 'constructor exec', content: 'BaseComponent class instance' }); } _name: string = ''; @Input() set name(n: string) { this.subject.next({ type: '@input', content: 'set base name update' }); this._name = n; } get name() { // 非必要不定义getter或者不放逻辑,拜访次数十分多 // this.subject.next({ type: 'tpl binding variable get', content: 'get name update' }); return this._name; } ngOnChanges() { this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngOnChanges' }); } ngOnInit() { this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngOnInit' }); } ngDoCheck() { this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngDoCheck' }); } ngAfterContentInit() { this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngAfterContentInit' }); } ngAfterContentChecked() { this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngAfterContentChecked' }); } ngAfterViewInit() { this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngAfterViewInit' }); } ngAfterViewChecked() { this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngAfterViewChecked' }); } ngOnDestroy() { this.subject.next({ type: 'lifecycle', content: 'BaseComponent ngOnDestroy' }); }}// HelloComponent 是AppComponent的子视图组件, 同时也是BaseComponent的子类...@Component({ selector: 'hello', template: `<h1>Hi, {{name}}!</h1>`, styles: [`h1 { font-family: Lato; }`],})export class HelloComponent extends BaseComponent { _name: string = ''; subject = window['subject']; @Input() set name(n: string) { this.subject.next({ type: '@input', content: 'set name update' }); this._name = n; } get name() { // this.subject.next({ type: 'template binding variable get', content: 'get name update' }); 仅演示调用 return this._name; } messages = []; constructor() { super(); this.subject.next({ type: 'constructor exec', content: 'class instance, 拜访@input属性name=' + this.name }); }}
执行后果
解释演绎
景象及实现状况
- 生命周期去掉了继承体系的HelloComponent中的实现,在基类BaseComponent中实现
- 无论如何结构器都是在最早执行并案依赖程序执行
- BaseComponent中的生命周期都执行了(咱们晓得继承后,这些生命周期办法在Hello组件中也是能够调用的,那Angular到底调用的是子类的还是基类的呢?请持续往下看)
持续批改源码:
// HelloComponent 是AppComponent的子视图组件, 同时也是BaseComponent的子类...@Component({ selector: 'hello', template: `<h1>Hi, {{name}}!</h1>`, styles: [`h1 { font-family: Lato; }`],})export class HelloComponent extends BaseComponent implements OnChanges, OnInit, DoCheck, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked, OnDestroy{ _name: string = ''; subject = window['subject']; @Input() set name(n: string) { this.subject.next({ type: '@input', content: 'set name update' }); this._name = n; } get name() { // this.subject.next({ type: 'template binding variable get', content: 'get name update' }); 仅演示调用 return this._name; } messages = []; constructor() { super(); this.subject.next({ type: 'constructor exec', content: 'class instance, 拜访@input属性name=' + this.name }); } ngOnChanges() { this.subject.next({ type: 'lifecycle', content: 'ngOnChanges, 拜访@input属性name=' + this.name }); } ngOnInit() { this.subject.next({ type: 'lifecycle', content: 'ngOnInit, 拜访@input属性name=' + this.name }); } ngDoCheck() { this.subject.next({ type: 'lifecycle', content: 'ngDoCheck, 拜访@input属性name=' + this.name }); } ngAfterContentInit() { this.subject.next({ type: 'lifecycle', content: 'ngAfterContentInit, 拜访@input属性name=' + this.name }); } ngAfterContentChecked() { this.subject.next({ type: 'lifecycle', content: 'ngAfterContentChecked, 拜访@input属性name=' + this.name }); } ngAfterViewInit() { this.subject.next({ type: 'lifecycle', content: 'ngAfterViewInit, 拜访@input属性name=' + this.name }); } ngAfterViewChecked() { this.subject.next({ ype: 'lifecycle', content: 'ngAfterViewChecked, 拜访@input属性name=' + this.name }); } ngOnDestroy() { this.subject.next({ type: 'lifecycle', content: 'ngOnDestroy, 拜访@input属性name=' + this.name }); }}
咱们为子类也实现这些生命周期,看看Angualr执行的是子类的还是父类的还是俩个都会执行,执行的程序如何?
执行后果
演绎总结:
景象是执行了子类的,父类的没有执行
- 阐明生命周期办法,子类和父类之间生命周期办法也合乎继承原理存在重写的状况,子类中的生命周期办法
模板绑定变量获取的状况
最初顺道再看一下模板绑定的变量Angular获取的状况,如下图
get name() { // 只须要把name的getter中的正文去掉即可 this.subject.next({ type: 'template binding variable get', content: 'get name update' }); 仅演示调用 return this._name; } // 另一个,须要把生命周期钩子中打印name字段读取去掉,这样咱们就晓得name被Angular读取了几次,并在什么时候读取。(有时候,咱们会在getter中写一些简略的逻辑,把变量作为计算属性,理解这个对咱们通晓name被读取的数量,有很大用途)
解释并演绎
- 父组件将name属性传递给Hello组件(@input执行)后,Hello组件将本人的内容ngAfterContentChecked后获取name, 并在App组件ngAfterViewChecked后读取了name的值,整个过程读取了俩次。
最初再附上一张对于联合上述三个组件的Angular我的项目的运行以及生命周期执行运行图
最初,如果大家对上述表述或者论断存在质疑或者不解,能够在上面留言。