Angular 变更检测及单向数据流
本文学习总结于
数据在Angular中,我们所说的数据,即组件持有的数据模型。 import { Component } from '@angular/core'; import {Course} from "./course"; export interface Course { id:number; description:string; } @Component({ selector: 'app-root',template: ` <div class="course"> <span class="description">{{course.description}}</span> </div> `}) export class AppComponent { course: Course = { id: 1,description: "Angular For Beginners" }; } Angular通过模板把数据模型转换成视图。 模板在Angular内部的使用Angular应用中,模板指的的是@Component装饰器的template或templateUrl指向的HTML页面 @Component({ selector: 'app-root',description: "Angular For Beginners" }; } 在模板出现问题时,我们会收到有用的错误消息。 所以很明显Angular不是简单地用一个字符串来处理模板。 那么这是如何工作的?
实际上,Angular把取数据模型应用于一个函数(DOM component renderer)。 该函数的输出是对应于此HTML模板的DOM数据结构。 该函数的定义大体如下: View_AppComponent_0.prototype.createInternal = function(rootSelector) { var self = this; var parentRenderNode = self.renderer.createViewRoot(self.parentElement); self._text_0 = self.renderer.createText(parentRenderNode,'n',self.debug(0,0)); self._el_1 = jit_createRenderElement5(self.renderer,parentRenderNode,'div',new jit_InlineArray26(2,'class','course'),self.debug(1,1,0)); self._text_2 = self.renderer.createText(self._el_1,'nn ',self.debug(2,20)); self._el_3 = jit_createRenderElement5(self.renderer,self._el_1,'span','description'),self.debug(3,3,4)); self._text_4 = self.renderer.createText(self._el_3,'',self.debug(4,30)); self._text_5 = self.renderer.createText(self._el_1,'nn',self.debug(5,59)); self._text_6 = self.renderer.createText(parentRenderNode,self.debug(6,5,6)); self.init(null,(self.renderer.directRenderer? null: [ self._text_0,self._text_2,self._el_3,self._text_4,self._text_5,self._text_6 ] ),null); return null; }; 从其中createViewRoot,createText一些方法和parentElement,parentRenderNode命名,可以大概知道该函数正在创建一个DOM数据。 一旦数据状态发生改变,Angular数据检测器检测到,将重新调用 如何查看自己的组件的DOM component renderer,以及该函数的产生时机,请参考原文中的Where can I find this function for my components ? 和 When is this code generated ? 引起数据模型变化的来源数据模型一旦发生改变,视图就要相应发生变化,这也是现在流行的Model Driven View。那么就客户端(浏览器)来说,引起数据模型发生变化的事件源有:
这些事件源有一个共同的特性,即它们都是异步操作。那我们可以这样认为,所有的异步操作都有可能会引起模型的变化。 变更检测和单向数据流规则每一个异步操作都有可能引起数据状态的变更,Angular封装 Zone来拦截跟踪异步。
为什么要单向数据流?我们希望确保在将数据转换为视图的过程中,不会进一步修改数据。数据从组件类流向代表它们的DOM数据结构,生成这些DOM数据结构的行为本身不会对数据进行进一步修改。但在Angular的变更检测周期中,组件的生命周期钩子会被调用,这意味着我们编写的代码在该过程中被调用,该代码有可能引发数据状态发生改变。
例如 import {Component,AfterViewChecked} from '@angular/core'; import {Course} from "./course"; @Component({ selector: 'app-root',template: ` <div class="course"> <span class="description">{{course.description}}</span> </div> `}) export class AppComponent implements AfterViewChecked { course: Course = { id: 1,description: "Angular For Beginners" }; ngAfterViewChecked() { this.course.description += Math.random(); } } 上述代码会在Angular变更检测周期发生错误。我们在该组件ngAfterViewChecked()方法中修改了数据状态。导致了视图渲染后,数据跟视图状态不一致。 解决: ngAfterViewChecked() { setTimeout(() => { this.course.description += Math.random(); }); } 我们可以使用setTimeout将数据修改延迟到下一个变更周期。 除了组件生命周期回调的钩子可能触发数据状态的改变还有其他, import { Component } from '@angular/core'; import {Course} from "./course"; @Component({ selector: 'app-root',template: ` <div class="course"> <span class="description">{{description}}</span> </div> `}) export class AppComponent { course: Course = { id: 1,description: "Angular For Beginners" }; get description() { return this.course.description + Math.random(); } } Angular每次检测description时,它都会返回一个不同值。
如果Angular没有制止该行为,数据和视图会保持在不一致的状态,其中渲染过程完成后的视图不反映数据的实际状态。或者重复检测,直到数据稳定可能会导致性能问题。 单向数据流重要性
变化检测性能优化变化检测前:
变化检测时:
每次变化检测都是从根组件开始,从上往下执行,遍历每个组件。
OnPush变化检测策略OnPush 策略:若输入属性没有发生变化,组件的变化检测将会被跳过 OnPush变化检测策略+Immutable
当对象属性值改变,但对其引用没改变,Angular会默该改数据没发生变化。 实践例子可参考:Angular 2 Change Detection - 2 的OnPush策略章节。 因此当我们使用 OnPush 策略时,需要使用的 Immutable 的数据结构(Immutable 即不可变,表示当数据模型发生变化的时候,我们不会修改原有的数据模型,而是创建一个新的数据模型),才能保证程序正常运行。 为了提高变化检测的性能,我们应该尽可能在组件中使用 OnPush 策略,为此我们组件中所需的数据,应仅依赖于输入属性。 OnPush变化检测策略+Observable使用 immutable 时 change detection cycle 依旧从 root component 开始往下,依次检测。 而使用 OnPush变化检测策略 和 Observable 时,情况就不一样了,它的变更很可能是从一个非常下层的子 component 中开始发生的,比如:
ChangeDetectorRefChangeDetectorRef 是组件的变化检测器的引用,我们可以在组件中的通过依赖注入的方式来获取该对象,来手动控制组件的变化检测行为: ChangeDetectorRef 变化检测类中主要方法有以下几个: export abstract class ChangeDetectorRef { abstract markForCheck(): void; abstract detach(): void; abstract detectChanges(): void; abstract reattach(): void; } 其中各个方法的功能介绍如下:
import { Component,Input,OnInit,ChangeDetectionStrategy,ChangeDetectorRef } from '@angular/core'; import { Observable } from 'rxjs/Rx'; @Component({ selector: 'exe-counter',template: ` <p>当前值: {{ counter }}</p> `,changeDetection: ChangeDetectionStrategy.OnPush }) export class CounterComponent implements OnInit { counter: number = 0; @Input() addStream: Observable<any>; constructor(private cdRef: ChangeDetectorRef) { } ngOnInit() { this.addStream.subscribe(() => { this.counter++; this.cdRef.markForCheck(); }); } } 总结
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |