译 | Angular Ivy的变更检测执行:你准备好了吗?
Posted tc9011
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了译 | Angular Ivy的变更检测执行:你准备好了吗?相关的知识,希望对你有一定的参考价值。
原文链接:
https://blog.angularindepth.com/angular-ivy-change-detection-execution-are-you-prepared-ab68d4231f2c
让我们看看Angular为我们做了什么。
声明:这只是我对Angular新渲染器的学习之旅。
虽然新的Ivy渲染器的重要性还没有完全展现出来,但许多人想知道它将如何工作以及它为我们准备的变化。
在本文中,我将展示Ivy变更检测机制,展示一些让我非常兴奋的事情,并从头开始,根据指导(类似于Angular Ivy指导)构建简单的app。
首先,介绍一下我下面将研究的app:
@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/
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数组中。
Ivy从instructions创建LNodes,这个instructions被写入
ngComponentDef.template函数,并将它们存储在data数组中:
除了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等抽象方法。
当我们在组件构造函数中询问这个依赖关系时,我们实际上得到了继承 ChangeDetectorRef 类的ViewRef实例。
现在,我们来看看用于在Ivy中运行变更检测的内部方法。其中一些可用作公共API(markViewDirty和detectChanges),但我不确定其他的API。
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);
}
用于安排整个应用程序的变更检测。与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;
});
}
}
标记当前视图和所有祖先视图为脏(译者注:脏为需要变更检测)。
在早期的Angular 5中,它只向上迭代并启用了所有父视图的检查,现在请注意,markForCheck的确触发了Ivy变更检测周期! 以上是关于译 | Angular Ivy的变更检测执行:你准备好了吗?的主要内容,如果未能解决你的问题,请参考以下文章 使用 Cypress 触发 Angular Ivy 变化检测