Angular小博客

分享让你更聪明

[译2021]Ivy的内部数据结构

浏览:1次 日期:2024年10月20日 19:40:44 作者:admin

我认为深入了解 Angular 的新 Ivy 渲染引擎的内部工作原理会很有用。在本文中,我希望保持较高的水平,但同时提供有关 Ivy 如何在内部组织其数据结构以专注于内存性能的重要见解。

模板树、逻辑树和渲染树

当Ivy进行渲染时,它需要跟踪三种数据:模板、逻辑树和渲染树。为了简洁起见,在我们的许多数据结构中这三个概念被缩写为 T、L 和 R 前缀。

模板是源代码的解析版本。它包含以 Ivy 指令和有关组件/指令的元数据的形式呈现模板的指令。如果您可以在源代码中找到它,那么模板数据结构中也会存在相应的字段。无论其中的代码是否被执行,模板信息都存在。例如,即使条件为假,*ngIf 后面的模板仍将具有此模板信息。在ivy中,模板信息存储在TView(以及TData和TNode)数据结构中。这些数据结构共同提供了 Ivy 模板在运行时所需的所有静态信息。 “静态”这个词很重要,可以将其与下一个 Ivy 概念:逻辑视图区分开来。

逻辑视图(LView)表示模板(TView)的实例。我们使用“逻辑”一词来强调开发人员如何从逻辑角度思考应用程序。 ParentComponent 包含 ChildComponent。从逻辑角度来看,我们认为 ParentComponent 包含 ChildComponent,因此是“逻辑”名称。 “逻辑”一词与渲染树的最终概念形成对比。

渲染树是实际的 DOM 渲染树。它与上面的逻辑树不同,渲染树必须考虑内容投影。因为这种父/子关系并不像逻辑视图中那样简单。

让我们看一个例子来巩固这些概念。

@Component({
  selector: ‘child’,
  template: ‘<span>I am a child.</span>’
})
class ChildComponent {}
@Component({
  selector: ‘parent’,
  template: ‘
    <div>
      projected content: 
      <ng-content></ng-content>
    </div>
  ’
})
class ParentComponent {}
@Component({
  selector: ‘demo’,
  template: `
    <parent id=”p1">
    <child id=”c1"></child>
    </parent>
    <child id=”c2"></child>
  `
})
class DemoApp {}

应用程序加载后(但在引导之前),Ivy 将上述代码解析到 TView 中。

注意:或多或少,这里描述的很多内容都是为了性能而延迟执行的,而且这是伪代码而不是实际执行。以下片段是伪代码。

const tChildComponentView = new TView(
  tData: [
    new TElementNode(‘span’),
    new TTextNode(‘I am a child.’),
  ],
  …,
);
const tParentComponentView = new TView(
  tData: [
    new TElementNode(‘div’),
    new TTextNode(‘projected content: ‘),
    new TProjectionNode(),
  ],
  …,
);
const tDemoAppView = new TView(
  tData: [
    new TElementNode(‘parent’, [‘id’, ‘p1’]),
    new TElementNode(‘child’, [‘id’, ‘c1’]),
    new TElementNode(‘child’, [‘id’, ‘c2’]),
  ],
  …,
)

下一步是引导(或实例化)应用程序。实例化涉及从 TView 创建 LView 实例。

const lParentComponentView_p1 = new LView(
  tParentComponentView,
  new ParentComponent(…),
  document.createElement(‘div’),
  document.createText(‘projected content: ‘),
);
const lChildComponentView_c1 = new LView(
  tChildComponentView,
  new ChildComponent(…),
  document.createElement(‘span’),
  document.createText(‘I am a child.’),
);
const lChildComponentView_c2 = new LView(
  tChildComponentView,
  new ChildComponent(…),
  document.createElement(‘span’),
  document.createText(‘I am a child.’),
);
const lDemoAppView = new LView(
  tDemoAppView,
  new DemoApp(…),
  document.createElement(‘parent’),
  lParentComponentView_p1,
  document.createElement(‘child’),
  lChildComponentView_c1,
  document.createElement(‘child’),
  lChildComponentView_c2,
)

上图展示了TView和LView的区别。看ChildComponent,TView只有一个实例,但是LView有两个实例,因为ChildComponent被使用了两次。另一个关键区别是 LView 仅存储特定于该组件实例的数据,例如组件实例和关联的 DOM 节点。 TView 存储在组件的所有实例之间共享的信息,例如需要创建哪些 DOM 节点。

在上面的示例中,LView 显示为一个类(new LView(…)。)实际上,我们将其存储为数组([…]。)将 LView 存储为数组是出于内存性能原因。每个模板都会有不同数量的 DOM 节点和子组件/指令,将其存储在数组中是最有效的方式。

使用数组进行存储的含义是,不清楚事物存储在数组中的哪个位置。 TData 的目的是描述 LView 中每个位置存储的内容。因此 LView 本身不足以进行推理,因为它存储的值没有上下文来说明这些值代表什么。 TView 描述了组件需要什么,但它不存储实例信息。通过将 LView 和 TView 放在一起,Ivy 可以访问并推理 LView 中的值。 LView 存储值,而 TView 存储 LView 中值的含义。

为了简化上面的示例,LView 仅存储 DOM 节点。实际上,LView 还存储绑定、注入器、消毒器以及与视图状态相关的任何其他内容(在 TView/TData 中具有相应的条目。)

思考 TView 和 LView 的一种方法是将这些概念与面向对象编程联系起来。 TView就像类,LView就像类实例。

系统中的所有LView形成一棵树。对于我们的例子,我们可以像这样想象它。

<#LView>
  <demo>
    <#LView: tDemoAppView, new DemoApp()>
      <parent id=”p1">
        <#LView: tParentComponentView, new ParentComponent()>
          <div>
            projected content: 
            <ng-content></ng-content>
          </div>
        </#LView>
        <child id=”c1">
          <#LView: tChildComponentView, new ChildComponent()>
            <span>
              I am a child.
            </span>
          </#LView>
        </child>
      </parent>
      <child id=”c2">
        <#LView: tChildComponentView, new ChildComponent()>
          <span>
            I am a child.
          </span>
        </#LView>
      </child>
    </#LView>
  </demo>
</#LView>

上面的“逻辑”树与下面所示的“渲染”树不同。主要区别在于,在“逻辑”树中,节点保持在一起,而在“渲染”树中,节点由于内容投影而分布。

<demo>
  <parent id=”p1">
    <div>
      projected content: 
      <child id=”c1">
        <span>
          I am a child.
        </span>
      </child>
    </div>
  </parent>
  <child id=”c2">
    <span>
      I am a child.
    </span>
  </child>
</demo>

我希望这篇文章能让您深入了解 Ivy 如何针对内存消耗进行优化。如果您喜欢这些类型的 Ivy 内部深入研究,请发表评论告诉我们。感谢您的阅读并感谢您成为 Angular 社区的一员。

原文链接:https://blog.angular.dev/ivys-internal-data-structures-f410509c7480