ES6模板字符串在HTML模板渲染中的应用

这篇文章发布于 2020年10月22日,星期四,01:29,归类于 JS实例。 阅读 26264 次, 今日 3 次 9 条评论

 

模板字面量HTML渲染

一、先了解下ES6模板字符串

过去拼接字符串多采用加号的形式,例如:

var data = {
    username: 'zhangxinxu'
};
console.log('本文作者是:' + data.username);

有两个不足,一是如果动态数据比较多,则字符串拼接的时候会出现大量的加号和引号,阅读起来会比较吃力,尤其在HTML拼接的代码中,因为HTML属性本身就包含一堆引号。

二是如果字符串包含换行符,则需要转义,不然会报错,例如下面代码,每一行的最后需要加一个斜杠,代码可读性实在不敢恭维:

var html = '';
arrData.forEach(function (obj, index) {
    html = html + '\
    <tr>\
        <td><input type="checkbox" value="' + obj.id + '"></td>\
        <td><div class="ell">' + obj.title + '</div></td>\
        <td>' + obj.time + '</td>\
        <td align="right">' + obj.comment + '</td>
    </tr>\
';
});
console.log(html);

ES6模板字符串可以大大缓解提高类似需求代码的可读性,例如:

let data = {
    username: 'zhangxinxu'
};
console.log(`本文作者是:${data.username}`);

可以直接换行,不需要担心语法错误。

let html = `${data.map(function (obj, index) {
  return `<tr>
    <td><input type="checkbox" value="${obj.id}"></td>
    <td><div class="ell">${obj.title}</div></td>
    <td>${obj.time}</td>
    <td align="right">${obj.comment}</td>
  </tr>`;
}).join('')}`;

console.log(html);

可以看出,上面基于ES6模板字符串拼接HTML片段的代码的可读性明显提升了很多,引号也干净了,换行也无需转义直接保留了。

但是,这个ES6模板字符串仍然有不足。

常规的字符拼接没问题,但是如果是HTML拼接,维护是个大问题。

因为HTML代码耦合到了JavaScript代码中,理论上HTML代码就应该在HTML页面或者模板页面中。

所以我就思考,是不是可以使用ES6模板字符串的语法,然后HTML模板还是在HTML页面中,而不是耦合在JavaScript中。

二、HTML模板变成JS模板的问题

比方说页面上有下面这段HTML代码:

<table id="tableMix" class="ui-table table">
<thead>
  <tr>
    <th width="20"><input type="checkbox"></th>
    <th>文章标题</th>
    <th width="22%">发布时间</th>
    <th width="15%" align="right">评论数</th>
  </tr>
</thead>
<tbody>
<template>
${data.map(function (obj, index) {
  return `<tr>
    <td><input type="checkbox" value="${obj.id}"></td>
    <td><div class="ell">${obj.title}</div></td>
    <td>${obj.time}</td>
    <td align="right">${obj.comment}</td>
  </tr>`;
}).join('')}
</template>
</tbody>
</table>

其中有个<template>元素,放置了列表渲染需要的模板数据,然后变量部分使用了ES6的模板字符串语法。

看起来似乎无需引入类似artTemplate.js这样的第3方的渲染引擎就可以实现我们的数据展示效果了。

但是,很遗憾,我们使用DOM API获取到的<template>元素中的内容是一个普通字符串,而不是模板字符串,例如:

console.log(document.querySelector('template').innerHTML);

返回的是普通的字符串内容。

返回的是普通字符串

此时的类似${obj.id}这样的内容就是个普通字符串,并非模板字符串,因此压根就没办法让里面的${obj.id}这样语法的字符变成动态的内容。

美好的想法似乎陷入了困境,难道就没有办法让普通的字符串变成模板字符串吗?

嘿!有办法的。

三、普通字符串转模板字符串

下面这段JavaScript代码可以让普通的字符串中的模板字符串语法转换成对应的数据并返回出来。

/**
 * Convert a string to a template-string
 * @param  {Object} params 模板数据
 * @return {String}        模板字符串语法解析后的字符串
 */
String.prototype.interpolate = function (params) {
    const names = Object.keys(params);
    const vals = Object.values(params);

    return new Function(...names, `return \`${this}\`;`)(...vals);
};

于是,上面列表的HTML模板内容想要渲染成真实的列表数据,只需要下面这行代码就可以了:

let htmlList = document.querySelector('template').innerHTML.interpolate(json)

其中json就是模板数据,通过请求后端接口返回。

只看代码可能不太清楚说的是什么,您可以狠狠地点击这里:ES6模板字符串语法作为HTML模板demo

下图就是使用interpolate()方法让使用了模板字符串语法的HTML模板变成需要的HTML代码片段后的效果:

平铺的模板效果

相应的JS业务代码如下所示:

// 业务处理代码
var eleTbody = document.querySelector('#tableMix tbody');
var strTemplate = eleTbody.querySelector('template').innerHTML;
// 获取数据
fetch('./ajax-article-list.php').then(res => {
    return res.json();    
}).then(json => {
    eleTbody.innerHTML = strTemplate.interpolate(json);    
});

其中json数据结构如下:

{
    "code": 0,
    "msg": "获取成功",
    "data": [{
        "id": "0001",
        "title": "如何让文字作为CSS背景图片显示?",
        "time": "2020年10月20日",
        "comment": 7
    }, {
        "id": "0002",
        "title": "SVG feTurbulence滤镜深入介绍",
        "time": "2020年10月17日",
        "comment": 3
    }, {
        "id": "0003",
        "title": "HTML enterkeyhint设置iOS/Android键盘enter键",
        "time": "2020年10月11日",
        "comment": 3
    }, {
        "id": "0004",
        "title": "快速学习CSS Color Level 4的色值新语法",
        "time": "2020年10月11日",
        "comment": 2
    }]    
}

可以看到,业务代码的实现非常的简洁。

于是乎,一个完全不需要应用第3方模板渲染引擎,也无需学习任何模板语法(因为使用的是原生ES6模板字符串语法)的HTML模板渲染功能就实现了。

特别适合用在非长期的,需要捷开发的项目中,例如运营活动页面,一些工具类页面,偏展示性的网站等。

四、一些限制

这种利用原生模板字符串语法进行HTML模板解析的技术虽然简洁高效,但是也是有一些限制的。

首先是兼容性的限制。

如果项目需要兼容IE浏览器,则需要斟酌一下了,我还不确定Babel转成ES5语法后功能是否还正常。

另外一个限制是不能使用箭头函数(注意,是作为HTML模板字符串内容时候不能使用箭头函数,在JavaScript中还是可以的),只支持常规的表达式语法。

例如下面的写法就会报错,只能使用function函数:

/* 报错 */
${data.map((obj, index) => {
  return `<tr>
    ...
  </tr>`;
}).join('')}

更新于2020-11-17

此模板转换方法是支持箭头函数的,前提是需要反转义下(by XboxYan)。

// HTML字符反转义   &lt;  =>  < 
function escape2Html(str) {
    var arrEntities = { 'lt': '<', 'gt': '>', 'nbsp': ' ', 'amp': '&', 'quot': '"' };
    return str.replace(/&(lt|gt|nbsp|amp|quot);/ig, function (all, t) {
        return arrEntities[t];
    });
}
String.prototype.interpolate = function (params) {
    const names = Object.keys(params);
    const vals = Object.values(params);
    return new Function(...names, `return \`${escape2Html(this)}\`;`)(...vals);
};

五、进一步的模板渲染方法

我的同事XboxYan在本文的interpolate()基础上进一步延伸,实现了一个极简的模板渲染引擎。

https://github.com/XboxYan/web-template

特点如下:

  1. 纯原生浏览器解析,无任何依赖,无需编译,不拖泥带水
  2. 类 vue 模板语法,上手快,几乎可以不用看文档
  3. 代码量极少,包含注释不到 100 行,方便学习和扩展

欢迎品鉴!

六、其他以及结语

本文的demo使用的<template>元素里面的内容本身是可以具有DOM特性获取的,是HTML5中的一个规范标签,IE浏览器不支持,包含其他特性,不仅仅是里面内容作为字符串获取,更详细内容可以参见这篇文章“HTML5 <template>标签元素简介”。

模板字符串(又称模板字面量 – Template literals)还有个标签模板的特性,也就是模板字符串如果紧跟在函数后面,则这个函数将被调用来处理这个模板字符串,作用是对模板数据进行精确控制与拼接。不过这个特性与本文内容关系不大,同时自己也没有发现这个特性有特别实用的应用场景,因此这里不做展开介绍。

有兴趣可以访问这里MDN文档关于模板字面量的介绍。

另外,其实早在2012年的时候,面向数据的编程文章我就写过几篇,例如其中一篇是“基于HTML模板和JSON数据的JavaScript交互”,目前已经有20多万的访问量了。这篇文章的核心与本文其实是类似的,但是实现形式上有了升级,以前使用的是<textarea>元素作为HTML模板容器,现在使用的是<template>元素;以前使用的是自己自定义的$$语法,且仅支持简单的对象字面量解析;现在使用的是浏览器原生的ES6模板字符串语法,且支持循环以及其他一些表达式。

思路和原理没变,技术的变化带来的实现上的提升。

好,以上就是本文的全部内容,让模板字符串的能力延伸到HTML模板字符串中,从而节约我们的开发成本,发挥JS新特性的潜力。

感谢阅读,欢迎分享。

(本篇完)

分享到:


发表评论(目前9 条评论)

  1. XboxYan说道:

    “是作为HTML模板字符串内容时候不能使用箭头函数” 应该是innerHTML的时候进行了转义,转换一下就好了

    $gt; => >

  2. paoche说道:

    学习到了

  3. silence0_o说道:

    思路不错,借用了新特性达到了模板的效果

  4. mfk说道:

    if/else也可以,我写的js是直接eval执行

  5. 王念一说道:

    最难受的是没法访问闭包域啊

  6. 说道:

    大佬,冒昧说下,修改原生对象的 prototype 和 new Function 都是不好的实践呀

  7. 鹿啦啦说道:

    一开始没有想明白为什么适用于偏展示性的页面,后来一想,我们的项目是实时刷新数据的,没办法用这种方式去写,就想明白了,毕竟模板内容在第一次插入数据的时候就被替换了~

    • 张 鑫旭说道:

      NoNoNo,偏展示性是因为模板逻辑不支持if else这类判断,无法满足复杂交互页面的需求。本文的这个模板技术实时刷新依然是可以实现的。