本文是不依赖框架和库的客户端JavaScript系列文章之一。我们的用意是帮助开发者记住可以仅用原生API和方法写出很棒的代码。另外,可以查看此前的文章 writing small JavaScript components without frameworks. 那篇文章有些基础,而这篇文章则打算探究模板和数据绑定,以及如何不使用框架去实践这些概念。
开发者依赖于框架去解决的一个主要问题是模板化标记和将数据插入模板的大量方法。在这里,我们探讨了几种使用原生JavaScript的常见模板操作。
HTML模板看起来很麻烦,有很多实现形式,特别是在浏览器中实现的情况下。可以从局部变量、远程服务器文件中得到HTML模板,也可以作为ajax请求的一部分,或者通过您最喜欢的框架的魔法将模板从代码中抽象出来。不管怎样,最终的模板只是一个带有标记的字符串,您可以使用它来进行一些查找和替换。在最原始的形式中,模板系统只不过是一个方法,它接受模板字符串和一个哈希项,并将哈希对应的数据粘贴到字符串中。这样的一个例子也许是render
方法,如下所示:
var insertable_markup = render('<h1>{{ the_title }}</h1>', {
the_title: 'hello, world!'
})
// insertable_markup is now '<h1>hello, world!</h1>'
上面的Render
方法可以做很多事情,但在这种情况下,所有需要我们做的是插值一些字符串。由于模板系统只是任意字符串替换,我们可以编写一个方法:
function render (template, options) {
return template.replace(/\{\{\s?(\w+)\s?\}\}/g, (match, variable) => {
return options[variable] || ''
})
}
在这个例子中,我们只有一个只对字符串进行全局替换的方法。它能匹配包含在大括号(\{\{}
中可以包含空格(\s?
)的任何单词(\w
)。第二个参数是一个函数,它的参数是整个匹配项(这里用不上)和括号中匹配的子字符串,而且这个子字符串还是options对象的一个属性。因此,使用一个3行的方法,我们便以非常基础的方式实现了“HTML模板”,它比任何复杂的框架方法都要快得多,并且能做到我们平时需要的一切。您可以在下面的示例中查看完整的测试代码:
在CodePen中查看jacopotarantino的 在ES6中编写一个简
以上只实现了将数据呈现到页面上,至于如何实现在object/model和DOM之间绑定数据,就是我们下一篇文章要讨论的了:“JavaScript without frameworks: Data Binding”。
警告: 这是一个反模式。可靠、可维护、迅速及可复用的HTML模板不允许访问内插数据中的嵌套属性。这是因为嵌套属性必须将传递到模板的数据结构绑定到模板本身上,使得重构更慢并且可靠性变差。没有必要在模板中进行类似于命名空间的操作,因为精心设计的模板永远不应该访问多个上下文。良好设计的组件通常也一样很小,因此它们不应该访问那么多的变量,也不需要命名空间。如果你将数据传递到不是扁平结构的模板(数据很少是扁平结构的),请确保在控制器或试图模型中重组数据。例如,在“员工ID卡”模板中,我们可能有如下数据:
const myEmployee = {
id: 'asdfsafdasdfasdfa',
name: {
first: 'Bob',
last: 'Builder'
},
role: 'Lead Engineer',
photos: {
primary: {
url: 'foobar.com/img.jpg',
description: 'A photo of Bob the Builder'
}
}
}
和一个看起来大致如下的模板:
<li data-id="{{ id }}">
<p>
<strong>{{ name }}</strong>
<small>{{ title }}</small>
</p>
</li>
为了准备这个数据,在控制器或视图层,我们需要把数据拉平如下:
const template_data = {
id: myEmployee.id,
image_url: myEmployee.photos.primary.url,
image_description: myEmployee.photos.primary.description,
name: `${myEmployee.name.first} ${myEmployee.name.last}`,
title: myEmployee.role,
}
这样传递给模板的数据非常简单,所以模板方法也不需要做很多工作,因此可以快速运行并可测试。但是如果我们无法平铺我们的数据呢?然后该怎样呢?我们不需要再控制器里去做数据转换。但就需要让render
方法稍微复杂一些了:
function render (template, options) {
return template.replace(/\{\{\s?([\w.]+)\s?\}\}/g, (match, variable) => {
return variable.split('.').reduce((previous, current) => {
return previous[current]
}, options) || ''
})
}
这次我们稍微改了一下正则表达式,以支持我们模板变量名称中的点号。模式[\w.]+
的意思是“匹配任何单词字符或点号的序列”。同时,替换也不再是简单地从options对象中按照属性取值,而是使用JavaScript把变量拆成各自独立的部分,再通过reduce取得序列中的最后一个值。这样稍有些复杂,但允许我们像下面这样在模板里使用嵌套属性:
<li data-id="{{ id }}">
<p>
<strong>{{ name.first }} {{ name.last }}</strong>
<small>{{ role }}</small>
</p>
</li>
查看完整的测试和示例如下:
See the Pen A template rendering method in ES6 with nested-property support by jacopotarantino (@jacopotarantino) on CodePen.
扫码关注w3ctech微信公众号
请问文章能转载到我们专栏(https://zhuanlan.zhihu.com/dreawer)吗?会注明来源的~
共收到2条回复