## 19.使用模板字面值和标签模板
> 原文: [http://exploringjs.com/impatient-js/ch_template-literals.html](http://exploringjs.com/impatient-js/ch_template-literals.html)
>
> 贡献者:[飞龙](https://github.com/wizardforcel)
在我们深入研究两个特性*模板字面值*和*标签模板*之前,让我们首先检查术语*模板*的多重含义。
### 19.1。消歧:“模板”
尽管所有名称中都有*模板*并且所有这些模板看起来都相似,但以下三件事情有很大不同:
* *网页模板*是从数据到文本的函数。它经常用于 Web 开发,通常通过文本文件定义。例如,以下文本定义了库 [Handlebars](https://handlebarsjs.com) 的模板:
```html
<div class="entry">
<h1>{{title}}</h1>
<div class="body">
{{body}}
</div>
</div>
```
* *模板字面值*是具有更多功能的字符串字面值,例如插值。它由反引号分隔:
```js
const num = 5;
assert.equal(`Count: ${num}!`, 'Count: 5!');
```
* *标签模板*是一个函数,后跟一个模板字面值。它导致调用该函数,并将模板字面值的内容作为参数提供给它。
```js
const getArgs = (...args) => args;
assert.deepEqual(
getArgs`Count: ${5}!`,
[['Count: ', '!'], 5] );
```
请注意,`getArgs()`接收字面值的文本和通过`${}`插入的数据。
### 19.2。模板字面值
与普通字符串字面值相比,模板字面值有两个主要好处。
首先,它们支持*字符串插值*:如果将表达式放在`${`和`}`中,则可以插入表达式:
```js
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!'});
```
其次,模板字面值可以跨越多行:
```js
const str = `this is
a text with
multiple lines`;
```
模板字面值总是产生字符串。
### 19.3。标签模板
行 A 中的表达式是*标签模板*:
```js
const first = 'Lisa';
const last = 'Simpson';
const result = tagFunction`Hello ${first} ${last}!`; // A
```
最后一行相当于:
```js
const result = tagFunction(['Hello ', ' ', '!'], first, last);
```
`tagFunction`的参数是:
* 模板字符串(第一个参数):一个包含插值(`${...}`)周围文本片段的数组。
* 在示例中:`['Hello ', ' ', '!']`
* 替换值(剩余参数):插值。
* 在示例中:`'Lisa'`和`'Simpson'`
字面值的静态(固定)部分(模板字符串)与动态部分(替换)分开。
`tagFunction`可以返回任意值,并接受模板字符串的两个视图作为输入(只有熟视图显示在上一个示例中):
* *熟视图*,其中:
* `\t`变为制表符
* `\\`变为一个反斜杠
* *原始视图*,其中:
* `\t`变为斜线后跟`t`
* `\\`变为两个反斜杠
原始视图通过`String.raw`(稍后描述)和类似的应用提供原始字符串字面值。
标签模板非常适合支持小型嵌入式语言(所谓的*领域特定语言*)。我们将继续举几个例子。
#### 19.3.1。标签函数库:lit-html
[lit-html](https://github.com/Polymer/lit-html) 是一个基于标签模板的模板库,由[前端框架 Polymer ](https://www.polymer-project.org/)使用:
```js
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-template-tag 是一个用于编写正则表达式的简单库。带有`re`标签的模板会生成正则表达式。主要的好处是你可以通过`${}`插入正则表达式和纯文本(参见`RE_DATE`):
```js
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 库](https://github.com/apollographql/graphql-tag) 允许您通过标签模板创建 GraphQL 查询:
```js
import gql from 'graphql-tag';
const query = gql`
{
user(id: 5) {
firstName
lastName
}
}
`;
```
此外,还有用于在 Babel,TypeScript 等中预编译此类查询的插件。
### 19.4。原始字符串字面值
原始字符串字面值通过标签函数`String.raw`实现。它们是一个字符串字面值,其中反斜杠不做任何特殊操作(例如转义字符等):
```js
assert.equal(String.raw`\back`, '\\back');
```
一个有帮助的例子是带有正则表达式的字符串:
```js
const regex1 = /^\./;
const regex2 = new RegExp('^\\.');
const regex3 = new RegExp(String.raw`^\.`);
```
所有三个正则表达式都是等价的,您可以看到使用字符串字面值,您必须编写两次反斜杠才能为该字面值转义它。使用原始字符串字面值,您不必这样做。
原始字符串字面值有用的另一个示例是 Windows 路径:
```js
const WIN_PATH = String.raw`C:\foo\bar`;
assert.equal(WIN_PATH, 'C:\\foo\\bar');
```
### 19.5。 (高级)
所有剩余部分都是高级的
### 19.6。多行模板字面值和缩进
如果将多行文本放在模板字面值中,则会出现两个目标冲突:一方面,文本应缩进来适合源代码。另一方面,它的行应该从最左边的列开始。
例如:
```js
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。修复:用于去缩进的模板标签
第一个修复是使用自定义模板标签来删除不需要的空格。它使用初始换行符后面的第一行来确定文本从哪一列开始,并去掉各处的缩进。它还删除了最开始的换行符和最后的缩进。这样的模板标签之一是 Desmond Brand 的[`dedent`](https://github.com/dmnd/dedent):
```js
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()`
第二个修复更快,但也更脏:
```js
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 模板从对象获取其数据,而模板字面值从变量获取其数据。解决方案是在函数体中使用模板字面值,其参数接收模板数据。例如:
```js
const tmpl = (data) => `Hello ${data.name}!`;
assert.equal(tmpl({name: 'Jane'}), 'Hello Jane!');
```
#### 19.7.1。一个更复杂的例子
作为一个更复杂的例子,我们想要一个地址数组并生成一个 HTML 表。这是数组:
```js
const addresses = [
{ first: '<Jane>', last: 'Bond' },
{ first: 'Lars', last: '<Croft>' },
];
```
生成 HTML 表的函数`tmpl()`如下所示。
```js
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()`来删除不必要的空格。
* 嵌套模板函数通过数组方法`.map()`(第 3 行)应用于 Array `addrs`的每个元素。
* 生成的(字符串)数组通过数组方法`.join()`(第 10 行)转换为字符串。
* 辅助函数`escapeHtml()`用于转义特殊 HTML 字符(第 6 行和第 7 行)。其实现将在下一节中介绍。
这是如何使用地址调用`tmpl()`并记录结果:
```js
console.log(tmpl(addresses));
```
输出是:
```html
<table>
<tr>
<td><Jane></td>
<td>Bond</td>
</tr><tr>
<td>Lars</td>
<td><Croft></td>
</tr>
</table>
```
#### 19.7.2。简单的 HTML 转义
```js
function escapeHtml(str) {
return str
.replace(/&/g, '&') // first!
.replace(/>/g, '>')
.replace(/</g, '<')
.replace(/"/g, '"')
.replace(/'/g, ''')
.replace(/`/g, '`')
;
}
```
![](https://img.kancloud.cn/3e/d5/3ed5755d562179ae6c199264f5e21157.svg) **练习:HTML 模板**
有奖练习挑战:`exercises/template-literals/templating_test.js`
### 19.8。进一步阅读
* [“探索 ES6”](http://exploringjs.com/es6/ch_template-literals.html)中描述了如何实现自己的标签函数。
![](https://img.kancloud.cn/ff/a8/ffa8e16628cad59b09c786b836722faa.svg) **测验**
参见[测验应用程序](ch_quizzes-exercises.html#quizzes)。
- I.背景
- 1.关于本书(ES2019 版)
- 2.常见问题:本书
- 3. JavaScript 的历史和演变
- 4.常见问题:JavaScript
- II.第一步
- 5.概览
- 6.语法
- 7.在控制台上打印信息(console.*)
- 8.断言 API
- 9.测验和练习入门
- III.变量和值
- 10.变量和赋值
- 11.值
- 12.运算符
- IV.原始值
- 13.非值undefined和null
- 14.布尔值
- 15.数字
- 16. Math
- 17. Unicode - 简要介绍(高级)
- 18.字符串
- 19.使用模板字面值和标记模板
- 20.符号
- V.控制流和数据流
- 21.控制流语句
- 22.异常处理
- 23.可调用值
- VI.模块化
- 24.模块
- 25.单个对象
- 26.原型链和类
- 七.集合
- 27.同步迭代
- 28.数组(Array)
- 29.类型化数组:处理二进制数据(高级)
- 30.映射(Map)
- 31. WeakMaps(WeakMap)
- 32.集(Set)
- 33. WeakSets(WeakSet)
- 34.解构
- 35.同步生成器(高级)
- 八.异步
- 36. JavaScript 中的异步编程
- 37.异步编程的 Promise
- 38.异步函数
- IX.更多标准库
- 39.正则表达式(RegExp)
- 40.日期(Date)
- 41.创建和解析 JSON(JSON)
- 42.其余章节在哪里?