## 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, '`')
;
}
```
 **練習:HTML 模板**
有獎練習挑戰:`exercises/template-literals/templating_test.js`
### 19.8。進一步閱讀
* [“探索 ES6”](http://exploringjs.com/es6/ch_template-literals.html)中描述了如何實現自己的標簽函數。
 **測驗**
參見[測驗應用程序](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.其余章節在哪里?