19.使用模板字面值和标记模板

原文: http://exploringjs.com/impatient-js/ch_template-literals.html

在我们深入研究两个特征 _ 模板字面值 _ 和 _ 标记模板 _ 之前,让我们首先检查术语 _ 模板 _ 的多重含义。

19.1。消歧:“模板”

尽管所有名称中都有 _ 模板 _ 并且所有这些模板看起来都相似,但以下三件事情有很大不同:

  • _ 网页模板 _ 是从数据到文本的功能。它经常用于 Web 开发,通常通过文本文件定义。例如,以下文本定义了库 Handlebars 的模板:

    <div class="entry">
      <h1>{{title}}</h1>
      <div class="body">
        {{body}}
      </div>
    </div>
    
  • _ 模板字面值 _ 是具有更多功能的字符串字面值。例如,插值。它由反引号分隔:

    const num = 5;
    assert.equal(`Count: ${num}!`, 'Count: 5!');
    
  • _ 标记模板 _ 是一个函数,后跟一个模板字面值。它导致调用该函数,并将模板字面值的内容作为参数提供给它。

    const getArgs = (...args) => args;
    assert.deepEqual(
      getArgs`Count: ${5}!`,
      [['Count: ', '!'], 5] );
    

    请注意,getArgs()接收字面值的文本和通过${}插入的数据。

19.2。模板字面值

与普通字符串字面值相比,模板字面值有两个主要好处。

首先,它们支持 _ 字符串插值 _:如果将表达式放在${}中,则可以插入表达式:

const MAX = 100;
function doSomeWork(x) {
  if (x > MAX) {
    throw new Error(`At most ${MAX} allowed: ${x}!`);
  }
  // ···
}
assert.throws(
  () => doSomeWork(101),
  {message: 'At most 100 allowed: 101!'});

其次,模板字面值可以跨越多行:

const str = `this is
a text with
multiple lines`;

模板字面值总是产生字符串。

19.3。标记的模板

A 行中的表达式是 _ 标记模板 _:

const first = 'Lisa';
const last = 'Simpson';

const result = tagFunction`Hello ${first} ${last}!`; // A

最后一行相当于:

const result = tagFunction(['Hello ', ' ', '!'], first, last);

tagFunction的参数是:

  • 模板字符串(第一个参数):一个包含插值周围文本片段的数组(${···})。
    • 在示例中:['Hello ', ' ', '!']
  • 替换(剩余参数):插值。
    • 在示例中:'Lisa''Simpson'

字面值的静态(固定)部分(模板字符串)与动态部分(替换)分开。

tagFunction可以返回任意值,并获取模板字符串的两个视图作为输入(只有熟视图显示在上一个示例中):

  • _ 熟视图 _ 其中,例如:
    • \t成为标签
    • \\成为一个反斜杠
  • _ 原始视图 _ 其中,例如:
    • \t变为斜线后跟t
    • \\变成两个反斜杠

原始视图通过String.raw(稍后描述)和类似的应用程序启用原始字符串字面值。

标记模板非常适合支持小型嵌入式语言(所谓的 _ 域特定语言 _)。我们将继续举几个例子。

19.3.1。标记函数库:lit-html

lit-html 是一个基于标记模板的模板库,由前端框架 Polymer 使用:

import {html, render} from 'lit-html';

const template = (items) => html`
  <ul>
    ${
      repeat(items,
        (item) => item.id,
        (item, index) => html`<li>${index}. ${item.name}</li>`
      )
    }
  </ul>
`;

repeat()是用于循环的自定义函数。它的第二个参数为第 3 个参数返回的值生成唯一键。请注意该参数使用的嵌套标记模板。

19.3.2。标记函数库:重新模板标记

re-template-tag 是一个用于编写正则表达式的简单库。用re标记的模板会生成正则表达式。主要的好处是你可以通过${}插入正则表达式和纯文本(参见RE_DATE):

import {re} from 're-template-tag';

const RE_YEAR = re`(?<year>[0-9]{4})`;
const RE_MONTH = re`(?<month>[0-9]{2})`;
const RE_DAY = re`(?<day>[0-9]{2})`;
const RE_DATE = re`/${RE_YEAR}-${RE_MONTH}-${RE_DAY}/u`;

const match = RE_DATE.exec('2017-01-27');
assert.equal(match.groups.year, '2017');

19.3.3。标记函数库:graphql-tag

库 graphql-tag 允许您通过标记模板创建 GraphQL 查询:

import gql from 'graphql-tag';

const query = gql`
  {
    user(id: 5) {
      firstName
      lastName
    }
  }
  `;

此外,还有用于在 Babel,TypeScript 等中预编译此类查询的插件。

19.4。原始字符串字面值

原始字符串字面值通过标记函数String.raw实现。它们是一个字符串字面值,其中反斜杠不做任何特殊操作(例如转义字符等):

assert.equal(String.raw`\back`, '\\back');

有帮助的一个例子是带有正则表达式的字符串:

const regex1 = /^\./;
const regex2 = new RegExp('^\\.');
const regex3 = new RegExp(String.raw`^\.`);

所有三个正则表达式都是等价的您可以看到使用字符串字面值,您必须编写两次反斜杠才能为该字面值转义它。使用原始字符串字面值,您不必这样做。

原始字符串字面值有用的另一个示例是 Windows 路径:

const WIN_PATH = String.raw`C:\foo\bar`;
assert.equal(WIN_PATH, 'C:\\foo\\bar');

19.5。 (高级)

所有剩余部分都是高级的

19.6。多行模板字面值和缩进

如果将多行文本放在模板字面值中,则会出现两个目标冲突:一方面,文本应缩进以适合源代码。另一方面,它的行应该从最左边的列开始。

例如:

function div(text) {
  return `
    <div>
      ${text}
    </div>
  `;
}
console.log('Output:');
console.log(div('Hello!')
  // Replace spaces with mid-dots:
  .replace(/ /g, '·')
  // Replace \n with #\n:
  .replace(/\n/g, '#\n'));

由于缩进,模板字面值很适合源代码。唉,输出也是缩进的。而且我们不希望开头的返回和返回加上最后两个空格。

Output:
#
····<div>#
······Hello!#
····</div>#
··

有两种方法可以解决这个问题:通过标记模板或修剪模板字面值的结果。

19.6.1。修复:用于 dedenting 的模板标记

第一个修复是使用自定义模板标记来删除不需要的空格。它使用初始换行符后面的第一行来确定文本在哪一列开始并切断各处的缩进。它还删除了最开始的换行符和最后的缩进。一个这样的模板标签是 Desmond Brand 的 dedent

import dedent from 'dedent';
function divDedented(text) {
  return dedent`
    <div>
      ${text}
    </div>
  `;
}
console.log('Output:');
console.log(divDedented('Hello!'));

这次,输出没有缩进:

Output:
<div>
  Hello!
</div>

19.6.2。修复:.trim()

第二个修复更快,但也更脏:

function divDedented(text) {
  return `
<div>
  ${text}
</div>
  `.trim();
}
console.log('Output:');
console.log(divDedented('Hello!'));

字符串方法.trim()在开头和结尾删除多余的空格,但内容本身必须从最左边的列开始。此解决方案的优点是不需要自定义标记功能。缺点是它看起来很难看。

输出看起来与dedent一样(但是,最后没有换行符):

Output:
<div>
  Hello!
</div>

19.7。通过模板字面值进行简单的模板化

虽然模板字面值看起来像 Web 模板,但是如何将它们用于(web)模板并不是很明显:Web 模板从对象获取其数据,而模板字面值从变量获取其数据。解决方案是在函数体中使用模板字面值,其参数接收模板数据。例如:

const tmpl = (data) => `Hello ${data.name}!`;
assert.equal(tmpl({name: 'Jane'}), 'Hello Jane!');

19.7.1。一个更复杂的例子

作为一个更复杂的例子,我们想要一个地址数组并生成一个 HTML 表。这是数组:

const addresses = [
  { first: '<Jane>', last: 'Bond' },
  { first: 'Lars', last: '<Croft>' },
];

生成 HTML 表的函数tmpl()如下所示。

const tmpl = (addrs) => `
<table>
  ${addrs.map(
    (addr) => `
      <tr>
        <td>${escapeHtml(addr.first)}</td>
        <td>${escapeHtml(addr.last)}</td>
      </tr>
      `.trim()
  ).join('')}
</table>
`.trim();

tmpl()采取以下步骤:

  • <table>内的文本是通过单个地址(第 4 行)的嵌套模板函数生成的。注意它最后如何使用字符串方法.trim()来删除不必要的空格。
  • 嵌套模板函数通过 Array 方法.map()(第 3 行)应用于 Array addrs的每个元素。
  • 生成的数组(字符串)通过 Array 方法.join()(第 10 行)转换为字符串。
  • 辅助函数escapeHtml()用于转义特殊 HTML 字符(第 6 行和第 7 行)。其实现将在下一节中介绍。

这是如何使用地址调用tmpl()并记录结果:

console.log(tmpl(addresses));

输出是:

<table>
  <tr>
        <td>&lt;Jane&gt;</td>
        <td>Bond</td>
      </tr><tr>
        <td>Lars</td>
        <td>&lt;Croft&gt;</td>
      </tr>
</table>

19.7.2。简单的 HTML 转义

function escapeHtml(str) {
  return str
    .replace(/&/g, '&amp;') // first!
    .replace(/>/g, '&gt;')
    .replace(/</g, '&lt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&#39;')
    .replace(/`/g, '&#96;')
    ;
}

练习:HTML 模板

练习奖金挑战:exercises/template-literals/templating_test.js

19.8。进一步阅读

测验

参见测验应用程序


书籍推荐