去年做雪球的timeline模块时我正深受 #newTwitter 的影响,倾向于把尽可能多的逻辑放到客户端去做,最后实现的时候选择了Backbone.js。使用Backbone.js的好处就不说了,这一两年它火的一塌糊涂,到处都是介绍的文章,而且这篇文章的重点也不是这个。
下面我假设您已经了解Backbone.js的作用和实现方式。
在页面初始化的时候,与发起一个ajax请求去取初始数据相比,把初始数据输出到页面里是一个更好的方案。Backbone.js提供了一个Loading Bootstrapped Models的FAQ,雪球也正是这样做的。把初始数据的json输出到页面里,然后Backbone.js用这个json来渲染页面。
但是这一年的实践中陆续发现一些问题:接口输出的timeline json里某些字段里偶尔出现一些不可见的换行符,导致浏览器解析json的时候出错。输出json字符串有injection可能(后来今年三月份的时候backbone特意在文档里加上了提示)。另外,随着业务复杂性的增长,接口直接输出的json体积在膨胀,很多属性已经不是页面展示所必须的,json的体积已经接近甚至已经超过了生成的html的体积。
同时我还在思考另外一个问题,backbone的使用场景其实是app,DocumentCloud、Trello这种需要反复对页面元素操作,应用要处理好数据和UI的一致性,初始化的时候稍微慢一点也没有关系,用backbone再好不过了。但是雪球其实更像是page,用户打开页面希望尽早的看到数据,当然我们也需要经常操作页面的元素,也需要处理数据和UI的一致性问题。
在服务端把html就拼好然后传给浏览器似乎是最直接的答案。那么接下来就面临了这篇文章要处理的问题了,如果既用服务端渲染html,又能够继续使用现有的基于backbone的客户端程序,甚至可以随时切换渲染位置。
backbone的文档似乎没有明确的给出这样的建议,但是稍微思考一下backbone View的实现方式应该可以想到,既然view的events是绑在一个根el上面的,那么这个el是一个空的wrapper或者已经渲染好的html片段并不影响事件的delegate。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
|
假设上面的一段代码是已有的客户端渲染的实现方式,需要说明的是statusList
正是我们之前输出到页面里的timeline json,$("#timeline")
是准备放timeline的wrapper。
现在改成服务端渲染之后发生的变化的后果,$("#timeline")
变成了已经塞满status的列表,statusList
不再存在。我们挨个解决。
$("#timeline")
既然已经填满了,就不用再render啦。最后一行就改成了:
1 2 3 |
|
statusList
是空的,那么statusCollection
也是空的,comment的时候就找不到status的model。本来作为json输出来的数据其实被塞到了dom里,那我们就应该找一个合适的时候把status model从dom里读出来。我选择在view初始化的时候获取,给TimelineView加上initialize方法。
1 2 3 4 5 6 7 8 9 10 |
|
好了,客户端的代码没有任何其他要改的了,所有的backbone的功能都会跟原来一样的工作,还可以吧?
最后完整的代码改造成了这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
|
后记
- 这种处理对于需要SEO,或者特殊设备支持的应用来说更有意义。
- 服务端渲染html会给服务端带来额外的cpu消耗,但是很小。不过我们还是做了适配,可以随时切换渲染方式。
- 服务端渲染也需要模板,客户端渲染也需要模板,这个模板如何复用?对于使用node.js的雪球来说,很好处理。这个留给以后说。