框架 DOM 渲染问题探究

引言

使用NG初始化一个简单的Hello World!Sample

image.png

image.png

image.png

渲染完成后的DOM如下所示:

<app-root _nghost-ifx-c0="" ng-version="8.2.14">
  <app-hello-world _ngcontent-ifx-c0="" _nghost-ifx-c1="">
    <p _ngcontent-ifx-c1="">Hello World!</p>
  </app-hello-world>
</app-root>

根据渲染结果表明,渲染后的DOM中原组件节点<app-hello-world></app-hello-world>被保留,用于包裹组件模板内容。

当然这种设计是没问题的,因为上级可能给组件加属性,但是如果在某些情况下会很难处理。

<div class="page">
  <app-subject-preview></app-subject-preview>
</div>

还是试题预览的需求,循环遍历试题,使用了试题预览组件,试题预览渲染后的DOM是这样的。

<div class="page">
  <app-subject-preview>
    <p>完型填空大题干</p>
    <p>31. A.xx B.xx C.xx D.xx</p>
    <p>32. A.xx B.xx C.xx D.xx</p>
    <p>33. A.xx B.xx C.xx D.xx</p>
    <p>34. A.xx B.xx C.xx D.xx</p>
  </app-subject-preview>
</div>

子节点获取到的是一个个app-subject-preview,因分页时高度计算调用十分频繁,不易处理较繁重的计算任务,如果递归向下获取直到没有子元素时再处理,恐会产生性能问题。

期待生成的DOM如下,没有组件调用的那一层,易于处理:

<div class="page">
  <p>完型填空大题干</p>
  <p>31. A.xx B.xx C.xx D.xx</p>
  <p>32. A.xx B.xx C.xx D.xx</p>
  <p>33. A.xx B.xx C.xx D.xx</p>
  <p>34. A.xx B.xx C.xx D.xx</p>
</div>

实现

Github Issue

Github20170425日有人提出了该问题,希望和ng-container一样,认为组件的标签应该是可选的,可渲染成注释,不应该强制显示。至今官方未解决。

Components host-element should (optional) be a html-comment instead of html-element - Github

问题的讨论中提出了几个方案,使用ViewContainerRef,经测试无效。

一位老哥发帖参考了React,提出了Fragment的方案,个人觉得该方案非常好,但官方迟迟未采纳。

image.png

隔壁的 Vue

去看了一下隔壁Vue的渲染方式(Vue规范中不推荐用;)。

挂载一个Vue应用:

var app = new Vue({
  el: '#app'
})

创建一个Vue组件:

Vue.component('helloWorld', {
  template: '<p>Hello World!</p>'
})

HTML页面:

<body>
  <div id="app">
    <hello-world></hello-world>
  </div>
</body>

image.png

DOM渲染结果:

<body>
  <div id="app">
    <p>Hello World!</p>
  </div>
</body>

这种渲染结果算是比较理想的,但组件必须存在一个根标签也会造成额外的问题,强制组件有根标签也会造成多一层的问题。

将组件的p标签去掉,会产生如下错误:

image.png

image.png

思考

二者在创建组件时都需要组件拥有根元素,Angular是保留了原组件标签作为根元素,Vue是强制组件声明一个根标签作为根元素,两者实现类似。

我没有阅读过AngularVue的框架源码,但从官方迟迟未解决该问题,且多款框架都采用类似方式实现来推测,应该与检测算法有关,多个根元素可能不利于检测算法的实现。

ng-container

退而求其次,只能弃用组件的方式,手动通过ng-template/ng-container实现。

这里我推荐使用ng-container来减少循环带来的层级嵌套问题,使用方式与正常指令使用一致。

<ng-container *ngFor="let item of arr">
  {{ item }}
</ng-container>
<div *ngFor="let item of arr">
  {{ item }}
</div>

image.png

DOM渲染结果如下所示(所有的ng-container都被渲染为注释):

<app-root _nghost-ohv-c0="" ng-version="8.2.14">
  <!--bindings={
    "ng-reflect-ng-for-of": "0,1,2,3,4,5,6,7,8,9"
  }-->
    <!----> 0
    <!----> 1
    <!----> 2
    <!----> 3
    <!----> 4
    <!----> 5
    <!----> 6
    <!----> 7
    <!----> 8
    <!----> 9
  <!--bindings={
    "ng-reflect-ng-for-of": "0,1,2,3,4,5,6,7,8,9"
  }-->
  <div _ngcontent-ohv-c0="">0</div>
  <div _ngcontent-ohv-c0="">1</div>
  <div _ngcontent-ohv-c0="">2</div>
  <div _ngcontent-ohv-c0="">3</div>
  <div _ngcontent-ohv-c0="">4</div>
  <div _ngcontent-ohv-c0="">5</div>
  <div _ngcontent-ohv-c0="">6</div>
  <div _ngcontent-ohv-c0="">7</div>
  <div _ngcontent-ohv-c0="">8</div>
  <div _ngcontent-ohv-c0="">9</div>
</app-root>

总结

希望官方可以关注这个问题,并及时解决。

版权声明

本文作者:河北工业大学梦云智开发团队 - 张喜硕
阅读 2.6k

推荐阅读
Tomorrow
用户专栏

明天,你好

317 人关注
137 篇文章
专栏主页