多应用+插件架构,代码干净,二开方便,首家独创一键云编译技术,文档视频完善,免费商用码云13.8K 广告
## 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>&lt;Jane&gt;</td> <td>Bond</td> </tr><tr> <td>Lars</td> <td>&lt;Croft&gt;</td> </tr> </table> ``` #### 19.7.2。简单的 HTML 转义 ```js function escapeHtml(str) { return str .replace(/&/g, '&amp;') // first! .replace(/>/g, '&gt;') .replace(/</g, '&lt;') .replace(/"/g, '&quot;') .replace(/'/g, '&#39;') .replace(/`/g, '&#96;') ; } ``` ![](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)。