angular单向数据流&生命周期函数

angular

  • angular 中的变化检测,如果其中任意一个自组件发生状态改变,需要进行状态检测的话,都会引起从 root component 到最后一个 child component 的变化检测,直到最后一个组件也完成状态检测达到稳定状态为止。

在这个从上到下的变化检测流中,一旦 child A component 中的变化检测已经完成了,任何在 GrandChild component 或者更低层级的 component 都不允许去改变 child A component 中的属性

这个过程就是 angular 的单向数据流。

但是在 GrandChild component 里,有些钩子函数通过@Output 去改变 child A component 的数据值,是允许的,而有些又不允许。

允许的生命周期函数:

把 ngOnInit 换成 ngDoCheck、ngAfterContentInit、ngAfterContentChecked、ngOnChanges,效果是一样的,在 ChildAComponent 中 message 都能正常显示也不会报错。

不允许的生命周期周期函数:

ngAfterViewInit ,ngAfterViewChecked

这个时候会发现在 console 里会有 ExpressionChangedAfterItHasBeenCheckedError

出现这种错误的原因

在 angular 中强制了单向数据流,当有变化的时候,变化检测机制是沿着 component 关系树结构从上到下执行,直到最后一个 child component 变化检测完成,这个完整的变化检测才算结束。在这个过程中,parent component 的变化检测完成以后,任何更低一层级去改上一层级的属性,都不允许。如果是在生产环境里,也就是启用了 enableProdMode()会直接忽略这样的操作,页面也不会显示变化以后的值,也不会报错。但是在开发模式下,在每一次变换检测(change detection)以后,angular 会从上到下再多跑一个变化检测,确保每次改动之后所有的状态是 stable 的,这个时候发现有低层级改动上一层级的值,就会出现上面那个错误。

那为什么在 ngAfterViewInit 和 ngAfterViewChecked 会报错,而且其他几个钩子函数里不报错呢?

把 checkAndUpdateView 方法简化一下:

function checkAndUpdateView(view, ...) {
    ...
    // update input bindings on child views (components) & directives,
    // call NgOnInit, NgDoCheck, ngOnChanges,ngAfterContentInit, ngAfterContentChecked hooks if needed
    Services.updateDirectives(view, CheckType.CheckAndUpdate);

    // DOM updates, perform rendering for the current view (component)
    Services.updateRenderer(view, CheckType.CheckAndUpdate);

    // run change detection on child views (components)
    execComponentViewsAction(view, ViewAction.CheckAndUpdate);

    // call AfterViewChecked and AfterViewInit hooks
    callLifecycleHooksChildrenFirst(…, NodeFlags.AfterViewChecked…);
    ...
}

从上面的代码可以看出来,在 GrandChild component 中,AfterViewChecked 和 AfterViewInit 是在它自己的变化检测(change detetion)之后再执行的,也就是它状态 stable 之后再执行的,这时候在去触发它上一层级属性的改动,被认为是违反 angular 的单向数据流。

从上面的分析可以看到:angular 在变化检测(change detection)过程中也会去触发生命周期钩子函数。比较有意思的是,有些钩子函数是在 DOM Rending/change detection 之前触发,有些是在之后触发。

  • angular 整个渲染页面的过程是
  1. 更新 child component 的 input bindings,然后会触发 child component 中 OnInit、DoCheck、OnChanges 函数,如果页面有 ng-content,相应也会触发 ngAfterContentInit 和 ngAfterContentChecked。

  2. angular 会继续渲染当前 component 也就是 parent component 页面。

  3. 触发 child component 中的变化检测(change detection)。

  4. 触发 child component 中的 AfterViewInit 和 theAfterViewChecked。

  • angular 的生命周期

OnChanges,OnInit,DoCheck,AfterContentInit,AfterContentChecked,AfterViewInit,AfterViewChecked,OnDestroy。

钩子函数就是在对应的生命周期之前加上 ng

  1. ngOnChanges:

当前 component/directive 的@Input/@Output 绑定的值发生变化的时候会触发这个函数,在 ngOnInit 之前执行。需要注意的是:如果@Input 是个对象,对象里面的数据改变但是引用没有变化是不会触发这个函数。

  1. ngOnInit:

ngOnInit:在第一个 ngOnChanges 之后触发,只执行一次。这个函数用来初始化页面内容。

  1. ngDoCheck:

第一种情况是:只要有任何 change detection(比如 click handlers, http requests, route changes…)都会执行 ngDoCheck;
第二种情况是:状态发生变化,angular 自己又不能捕获这个变化会触发 ngDoCheck

  1. ngAfterContentInit:

页面有用 ng-content 进行组件内容投射,在初始化的时候会执行一次这个函数。

  1. ngAfterContentChecked:

在每次检查投射内容的时候执行,ngDoCheck 调用之后都会触发这个函数。

  1. ngAfterViewInit:component 的页面或者是它的子页面初始的时候会执行一次这个函数。

  2. ngAfterViewChecked:在每次检查 compoent 页面或者它的子页面的时候执行,ngDoCheck 调用之后都会触发这个函数。

  3. ngOnDestroy:在 commponet 被销毁之前执行。

在使用钩子函数的时候需要注意以下几点:

  • constructor vs ngOnInit:在 constructor 里并不是所有数据都已经存在,比如@ContentChildren/@ContentChild/@ViewChildren/@ViewChild/@Input 在执行 constructor 的时候并不存在,相关代码最好都放在 ngOnInit。

  • ngOnChanges vs ngDoCheck:ngOnChanges 是在@Input 的值发生变化时触发;而 ngDoCheck 在每次 change detection 的时候都会触发或者是在状态发生变化 angular 自己又不能捕获时被触发。在用 ngDoCheck 的时候要非常小心,ngDoCheck 被触发的频率非常高,代码尽量精简,避免导致页面性能问题。

  • ngAfterContentInit vs ngAfterViewInit:跟 ng-content 相关的就用 ngAfterContentInit,当前 component 或者它的 child componet 相关的就用 ngAfterViewInit。

  • ngAfterContentChecked vs ngAfterViewChecked: 这两个函数也是每次都在 change detection 会被执行,用的时候也需要小心,避免页面性能问题。

  • ngOnChanges vs ngAfterViewChecked: 可以复用的 Component 考虑 ngOnChanges,不可以复用的 component 考虑 ngAfterViewChecked。

你可能感兴趣的:(angular单向数据流&生命周期函数)