WsztRush

模板语言设计

平时各种模板用的很HIGH,但是当有一天遇到一个特殊需求,貌似现在的各种模板都不是那么好用,那么就不得不系统地思考一下模板语言应该如何设计~

Veolcity

做过WEB开发的JAVAER可能都这么干过:

String data = "<body>hello world</body>";
OutputStream outputStream = response.getOutputStream();
response.setHeader("content-type", "text/html;charset=UTF-8");
outputStream.write(data.getByte("UTF-8"));

简单的输出没问题,稍微复杂一点就不行了:

  1. 程序复杂
  2. 代码几乎没有可读性和可维护性

在使用Velocity之后情况有了明显的好转,当你想输出一个列表的时候可以这么干:

<ul>
	#foreach($i in $list)
	<li>$i</li>
	#end
</ul>

这种写法非常直观以至于我现在都用它来渲染SQL语句,如果你也想这么玩需要自己定义ResourceLoader来自定义资源加载。编写Velocity模板时只需要记住两个关键点:

  1. 所有的控制结构是以#开头
  2. 所有的取数据逻辑以$开头(恰好你也在用jQuery的话会产生冲突)

而其他的部分都会append到输出,使用时如果每次都对模板进行解析那速度估计就跟蜗牛一样,正确的姿势应该是编译并缓存(这些内容与主题关系不大就不说了)。

其中语法设计的核心思想是:

使用HTML(甚至普通文本)中很少用到的字符来区分语法结构

学习成本很低,但是单纯地使用它来编写一些复杂的逻辑还是很痛苦的事情。不过相比较来看FreeMarker的写法更让人难受,加个<有啥意义么(估计会被喷):

<ul>
	<#list list as i>
		<li>i</li>
	</#list>
</ul>

类似的模板引擎还有CommonTemplateHTTL等。虽然这些技术已经将性能提到到一个很高的水准,但在后端处理页面展示还是非常局限:

只能每次都获取全部的数据并把页面渲染一遍

浪费服务器资源不说,体验也很差,真是出力不讨好!

Angular

在JavaScript的世界里也有与Velocity相似的模板技术(更多可以看这里):

<h3>
<% if (typeof content === 'string'){ %>
	<%= content %>
<% } %>
</h3>

从Java转JavaScript可能觉得这种方式很好用:在数据发生变化的时候执行一下render然后替换掉原先改位置的DOM结构就可以了~ 但是能不能更进一步:

DOM结构随着数据的变化而自动跟着变化

看起来是终极目标,貌似Angular完美地实现了:

Your name: <input type="text" ng-model="yourname" placeholder="World">
<hr>
My name: <input type="text" ng-model="yourname" placeholder="World">

执行的效果为:

两个输入框的内容,你随便改变哪一个,另一个都会随之变化,这就是双向绑定(也许你已经注意到ng-model了),当你需要输出列表时要用到ng-repeat

<ul>
    <li ng-repeat="o in question.options">
        <b>\.</b>
        <input type="radio" name="optcheck" />
        \
    </li>
</ul>

这种思想非常先进,和之前出现的模板都是不一样的:

之前的模板都是静态的,像一锤子买卖,而Angular的模板是动态的!

刚开始接触前端的时候也想过这个问题,但是实在没有想出来应该如何定义这样的模板语言,而Angular则已经实现了,但是代价就是限制多、门槛高:

  1. 模块
  2. 控制器
  3. 过滤器
  4. 指令
  5. 作用域

可能大家不明白为什么门槛高:当你在适合Angular的例子上操作的时候上手非常容易,但是实现复杂的功能需要熟悉很多不那么直观的概念。还有一点比较不喜欢的是:

将展示和数据完全分开,甚至模板与展示相关的判断逻辑也分开!

这样确实能保持模板的简洁,但是总体上是否简洁、直观就不好说了。甚至连双向绑定这么好的卖点都有时候会被吐槽:

在页面复杂的时候双向数据绑定的行为可能是预测不出来(这点保留意见,没有深入玩过)。

React

接着我们来看下最近红得发紫的React,网上已经有很多它与Angular的比较,有些还是有点道理的。使用React的第一关是JSX语法:

var root =(
  <ul className="my-list">
    <li>First Text Content</li>
    <li>Second Text Content</li>
  </ul>
);

看起来就是将HTML代码嵌入到JavaScript中,看起来很怪但是也比较容易理解,而真正得到的代码如下:

var root = React.createElement(
    "ul",
    { className: "my-list" },
    React.createElement("li", null, "First Text Content"),
    React.createElement("li", null, "Second Text Content")
);

当然你也可以在babel在线工具来体验这种语法。

看起来很美好,但实际上也不能太任性:模板的作用仅在于映射!控制语句for等是不能使用(多用babel玩一下就能体会到从JSX到JS之间的转换有多简单):

思路有点像Angular那样去扩展HTML原有的东西(Angular扩展的是Attribute,而React进一步扩展了Element)!

在用React的时候需要过的第二关是生命周期,讲道理的话生命周期这种东西应该越简单越合理,然而并不是这样:

状态 含义
Mounting 已插入真实 DOM
Updating 正在被重新渲染
Unmounting 已移出真实 DOM

为每个状态配了两个处理函数:will函数在进入状态之前调用,did函数在进入状态之后调用:

  1. componentWillMount
  2. componentDidMount
  3. componentWillUpdate
  4. componentDidUpdate
  5. componentWillUnmount

另外提供两个特殊状态的处理函数:

函数 作用
componentWillReceiveProps 已加载组件收到新的参数时调用
shouldComponentUpdate 组件判断是否重新渲染时调用

大家都在讲React很快、非常快,这就是第三关的虚拟DOM:真实的DOM操作代价太大,在render会先操作内存中的DOM结构,然后最小化反映到真正的DOM上(DomDiff算法可以在这里感受下)。

Noob Template

在上面我们看到的各种办法把逻辑与HTML代码分开,如果是JS的话:

HTML的<和JavaScript的{其实已经天然地起到了这个作用!

下面这段代码不用说也应该可以猜到输出应该是什么吧:

<ul>
    for(var i = 0; i < 10; i++){
        <li>${i}</li>
    }
</ul>

当用组件来搭建一个页面的时候可以是这样(包含嵌套的逻辑):

@xxxxxx
    @yyyyyy
        ...
    @yyyyyy
        ...
    @yyyyyy
        ...

用过MarkDown或者Jade或者Python的同学可能对这种方式已经比较熟悉了,都没用过的话可以对比一下几种层级表示方式:

  1. 缩进式
  2. 大括号式
  3. END结束符式

使用组件时需要设置一些属性来控制其行为:

@xxxxx(name="TEST" style= list=ajax("/url.do"))

另外如果可以在模板中直接编写JavaScript代码就更灵活了:

@xxxxx
    @on(init)
        this.name = "TEST";
        this.style = {color:"white"};
        ....

然后结合React的精华:

最小化DOM操作(用这个模式实现起来好像不怎么方便)

那么这样实现的模板怎么样?

总结

在后端渲染页面来展示的方式有点像漫画:看完一页翻一页;每次改变数据刷一次页面有点像动画:一帧一帧地动。

参考资料:

  1. 走进AngularJs(一)angular基本概念的认识与实战
  2. 走进AngularJs(二) ng模板中常用指令的使用方式
  3. 2015年的JavaScript:Angular之类的框架将被库取代
  4. AngularJS 作用域与数据绑定机制
  5. 我是怎么从顾虑到热爱ReactJS的(与AngularJS经典MVC数据绑定的对比)
  6. 深入浅出React(三):理解JSX和组件
  7. 深入浅出React(四):虚拟DOM Diff算法解析
  8. 组件的详细说明和生命周期
  9. react.js的的diff算法真的很强大
  10. React 的 diff 算法
  11. React 入门实例教程
  12. React官网