yanni4night

优化 Ember 模板

yanni4night · 2016-09-05翻译 · 701阅读 原文链接

模块构成了你的 Ember 应用的 60%,然后呢?

用编译器或转译器处理源代码时,很容易让代码的源体积与真实线上的代码大小失去关联性。

这对于 Ember 的模板来说更是如此,很小的改动将带给生成的 JavaScript 的体量以深远的影响。

“每一比特都很重要。” —— 每一位 Web 前端开发者(在尝试使用其它方案优化应用而精疲力尽之后)

条件块是昂贵的

考虑渲染两个图形的一个组件模板,第二个利用 #if 来条件渲染:

<p class=”instructions1">{{instructionsOne}}</p>
{{#if instructionsTwo}}
  <p class=”instructions2">{{instructionsTwo}}</p>
{{/if}}

该组件生成的代码有 3.8kb。出奇地大!这些代码是从哪来的?

在构建期,每一个模板都转换为 JavaScript 模块。每个模块都包含用于创建 DOM 和计算其后代与值(如下例)的逻辑、代码和数据。

条件的 IF 块的编译方式是让人惊讶的地方。它转换为完整的子模板,几乎和父模板一样大,包含了元信息和它自己的渲染函数(红色部分)。

IF 条件块的模板生成的 JavaScript

{{#if}} 非常容易使用,它被认为是一种条件场景的轻量级解决方案。然而,在这之后,每一个条件块都是以一个完整的新模板的结构来表示的。

如果我们使用一个类名来有条件地隐藏这个图像元素,就会移除一半的代码,为应用节省了 1.2kb

<p class=”instructions1">{{instructionsOne}}</p>
<p class=”instructions2 {{unless instructionsTwo ‘hide’}}”>{{instructionsTwo}}</p>

对于优化模板来说这可能是一种有争议的方式,但是它体现了我们将要考察的一个普适性原理:微小的改动会节省大量代码,有时候会以可维护性和可读性为代价。

使用动态类来移一个完整的子模板

避免使用循环来重复 IF/Else

一个更复杂的模板包含了一系列的 If/Else 语句,来看生成的代码是如果变多的。每一个条件块转换为像上面例子中一样大的子模板。因此看起来像是一个模板,其实是 8 个

<section class=’instruments-container’>
  {{#if model.storageInstrument}}
    {{settings-instrument-item instrument=model.storageInstrument action=”manageBalance”}}
  {{/if}}

  {{#if combinedBankInstrument}}
    {{settings-instrument-item instrument=combinedBankInstrument action=”manageBankAccount”}}
  {{else if model.debitInstrument}}
    {{settings-instrument-item instrument=model.debitInstrument action=”removeOrReplaceInstrument”}}
  {{else if model.bankInstrument}}
    {{settings-instrument-item instrument=model.bankInstrument action=”removeOrReplaceInstrument”}}
  {{else}}
    {{settings-instrument-item type=”bankAccount” action=”addDebitCard”}}
  {{/if}}

  {{#if model.creditInstrument}}
    {{settings-instrument-item instrument=model.creditInstrument action=”removeOrReplaceInstrument”}}
  {{else}}
    {{settings-instrument-item type=”creditCard” action=”addCreditCard”}}
  {{/if}}
</section>

可以使用一个 #each 循环来重构这个组件,把创建 instrumentList 数组的行为移至 javascript 中:

<section class=’instruments-container’>
  {{#each instrumentList as |listItem|}}
    {{settings-instrument-item
          instrument=listItem.instrument
          action=listItem.action
          type=listItem.type
          action=listItem.action
          showAlertIcon=listItem.showAlertIcon }}
  {{/each}}
</section>

即使算上构建 instrumentList 的额外 JavaScript,新的实现也节省了 12kb 大小的代码。

可计算属性

另一个避免条件块的技术是把工作转移到可计算属性中。在下面的例子中,通过在 JavaScript 中计算显示值你节省了超过 50% 的组件尺寸。

模板中的 SVG

在模板中使用 SVG 同样会增加尺寸。这个 SVG 在一个圆圈中渲染了一个对号;源码是 298 比特,但是编译后是 2.2kb 的 JavaScript!

<svg viewBox=”0 0 300 300">
  <g>
    <path class=”circle animatable reverse” d=’M 150, 150 m -0, -150 a -150,-150 0 1,0 0,300 a -150,-150 0 1,0 0,-300' fill=”none” stroke-width=”19" />
    <path class=”check animatable” fill=”none” d=’M 90,160 l 45,45 l 90,-95' stroke-width=”19" />
  </g>
</svg>
  • 记得先要使用类似 SVGO 的工具减少 SVG 的体积;
  • 手动清理不使用的属性
  • 可以安全地省略 xmlnsversion 属性

更好的做法是,尝试将 SVG 移除到外部的文件中,并用背景图或者 <img> 标签来引用它。这会节省这个模板的开销,在 SVG 很少使用时这是一个明智的选择。有时候你甚至可以使用 CSS 创建同样的图像

使用组件的隐式标签

每一个 Ember 的组件都有一个隐式的包装标签,用于样式和布局。即使没在模板中定义,你也可以使用组件的 JavaScript 文件自定义它。

在上面的循环例子中,我们可以通过修改组件来优化包装标签 <section class=’instruments-container’>

export default Ember.Component.extend({
  tagName: 'section',
  classNames: ['instruments-container'],
  layoutName: 'components/instruments-container',
}

任何具有单独顶层元素的模板都可以使用隐式标签。该技术同样可以帮助减少 DOM 中的节点数量。

扩展组件而非包装组件

当向组件中增加新的功能时,容易让人去直接在新模板中包装基本的组件,中继所有的属性。对于创建新的模板文件时这是有负面影响的,可以通过扩展组件来避免:

import ValidatedInput from 'components/validated-input';

export default ValidatedInput.extend({
  placeholder: i18n.t('placeholders.email'),
  type: 'email'
});

百尺竿头

理解 Ember 的模板编译原理能够帮助你更直观地使用这些技术。一个更进一步的快速方法是以多种方式实现相同的模板,并对比结果。你可以使用一个差异化工具来看最终的输出,以及你的场景中哪一个是最合适的。祝你使用模板顺利!

相关文章