译 | Angular Ivy的变更检测执行:你准备好了吗?

Posted tc9011

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了译 | Angular Ivy的变更检测执行:你准备好了吗?相关的知识,希望对你有一定的参考价值。

原文链接:

https://blog.angularindepth.com/angular-ivy-change-detection-execution-are-you-prepared-ab68d4231f2c



让我们看看Angular为我们做了什么。


声明:这只是我对Angular新渲染器的学习之旅。

  Angular视图引擎的演变


译 | Angular Ivy的变更检测执行:你准备好了吗?


虽然新的Ivy渲染器的重要性还没有完全展现出来,但许多人想知道它将如何工作以及它为我们准备的变化。


在本文中,我将展示Ivy变更检测机制,展示一些让我非常兴奋的事情,并从头开始,根据指导(类似于Angular Ivy指导)构建简单的app。


译 | Angular Ivy的变更检测执行:你准备好了吗?


首先,介绍一下我下面将研究的app:


译 | Angular Ivy的变更检测执行:你准备好了吗?


@Component({
 selector: 'my-app',
 template: `
  <h2>Parent</h2>
  <child [prop1]="x"></child>
 `

})
export class AppComponent {
 x = 1;
}
@Component({
 selector: 'child',
 template: `
  <h2>Child {{ prop1 }}</h2>
  <sub-child [item]="3"></sub-child>
  <sub-child *ngFor="let item of items" [item]="item"></sub-child>
 `

})
export class ChildComponent {
 @Input() prop1: number;
 
 items = [1, 2];
}
@Component({
 selector: 'sub-child',
 template: `
  <h2 (click)="clicked.emit()">Sub-Child {{ item }}</h2>
  <input (input)="text = $event.target.value">
  <p>{{ text }}</p>
 `

})
export class SubChildComponent {
 @Input() item: number;
 @Output() clicked = new EventEmitter();
 text: string;
}


我创建了一个在线demo,用于了解Ivy如何在幕后运行:
https://alexzuza.github.io/ivy-cd/


译 | Angular Ivy的变更检测执行:你准备好了吗?


Demo使用了Angular 6.0.1 aot 编译器。你可以单击任何生命周期块来跳转到对应的代码。


为了运行变更检测过程,只需在Sub-Child下面的输入框中输入一些内容即可。


/ 视图 /


当然,视图是Angular中主要的低级抽象。

对于我们的例子,我们会得到下面类似的结构:


Root view
  |
  |
___ AppComponent view
         |
         |
__ ChildComponent view
                |
                |
_ Embedded view
                |       |
                |       |_ SubChildComponent view
                |
                |
_ Embedded view
                |       |
                |       |_ SubChildComponent view  
                |
                |
_ SubChildComponent view


视图应该描述模板,以及它包含一些反映该模板结构的数据。

我们来看看ChildComponent视图。它有以下模板:


<h2>Child {{ prop1 }}</h2>
<sub-child [item]="3"></sub-child>
<sub-child *ngFor="let item of items" [item]="item"></sub-child>


当前视图引擎从视图定义工厂创建nodes并将它们存储在视图定义的nodes数组中。


译 | Angular Ivy的变更检测执行:你准备好了吗?


Ivy从instructions创建LNodes,这个instructions被写入

ngComponentDef.template函数,并将它们存储在data数组中:


译 | Angular Ivy的变更检测执行:你准备好了吗?


除了nodes之外,新视图还包含data数组中的绑定(参见上图中的data[4],data[5],data[6])。给定视图的所有绑定,从bindingStartIndex开始按照它们出现在模板中的顺序进行存储。


注意我如何从ChildComponent获取视图实例。ComponentInstance .__ ngHostLNode__包含对组件宿主节点的引用。 (另一种方法是注入ChangeDetectorRef)


在这种方式下,angular 会首先创建根视图,并在data数组索引0处定位宿主元素。


RootView
  data: [LNode]
            native: root component selector


然后遍历所有组件并为每个视图填充data数组。


/ 变更检测 /


众所周知,ChangeDetectorRef只是抽象类,具有诸如detectChanges,markForCheck等抽象方法。


译 | Angular Ivy的变更检测执行:你准备好了吗?


当我们在组件构造函数中询问这个依赖关系时,我们实际上得到了继承 ChangeDetectorRef 类的ViewRef实例。

现在,我们来看看用于在Ivy中运行变更检测的内部方法。其中一些可用作公共API(markViewDirty和detectChanges),但我不确定其他的API。


译 | Angular Ivy的变更检测执行:你准备好了吗?


detectChanges


detectChanges 是对组件(及其可能的子组件)同步执行变更检测。


这个函数在组件中以同步方式触发变更检测。应该没有什么理由直接调用此函数,执行变更检测的首选方法是使用markDirty(请参见下文),并等待调度程序在将来某个时间点调用此方法。这是因为单个用户操作通常会导致许多组件失效,并且在每个组件上同步调用变更检测效率低下。最好等到所有组件都标记为脏,然后在所有组件上执行单一变更检测。


tick


用于在整个应用程序上执行变更检测。


这相当于detectChanges,但是要在根组件上调用。另外,tick执行生命周期钩子,并根据它们的ChangeDetectionStrategy和dirtiness来有条件地检查组件。


export function tick<T>(component: T): void {
 const rootView = getRootView(component);
 const rootComponent = (rootView.context as RootContext).component;
 const hostNode = _getComponentHostLElementNode(rootComponent);

 ngDevMode && assertNotNull(hostNode.data, 'Component host node should be attached to an LView');
 renderComponentOrTemplate(hostNode, rootView, rootComponent);
}


scheduleTick


用于安排整个应用程序的变更检测。与tick不同,scheduleTick将多个调用合并为一个变更检测运行。当视图需要重新渲染时,通常通过调用markDirty间接调用它。


export function scheduleTick<T>(rootContext: RootContext) {
 if (rootContext.clean == _CLEAN_PROMISE) {
   let res: null|((val: null) => void);
   rootContext.clean = new Promise<null>((r) => res = r);
   rootContext.scheduler(() => {
     tick(rootContext.component);
     res !(null);
     rootContext.clean = _CLEAN_PROMISE;
   }
);
 }
}


markViewDirty(markForCheck)


标记当前视图和所有祖先视图为脏(译者注:脏为需要变更检测)。

在早期的Angular 5中,它只向上迭代并启用了所有父视图的检查,现在请注意,markForCheck的确触发了Ivy变更检测周期!

以上是关于译 | Angular Ivy的变更检测执行:你准备好了吗?的主要内容,如果未能解决你的问题,请参考以下文章

使用 Cypress 触发 Angular Ivy 变化检测

Angular 5.0 变更检测策略 VS React 的变更检测策略

深入分析 Angular 变更检测

angular变更检测和onPush策略

如何在 Angular2 中触发变更检测? [复制]

我在 Angular 变更检测中的断点未在 checkAndUpdateView() 上触发