Server-generated JavaScript Responses https://37signals.com/svn/posts/3697-server-generated-javascript-responses
这绝对是最强音的逆袭。你如果没点耐性,就直接接受吧。
Revised: 经过吕哥提醒,是 SJR,不是 RJS。但我怎么觉得这是一回事啊。。。。
我也非常赞同应该 RJS
Benefit #2: Less computational power needed on the client
说实话看到这条我觉得 DHH 是在逆历史潮流而动
最近在用 Angular,感觉 Rails 里面大部分组件已经不需要了(View/Helper 等),只需要提供 JSON 就行 当然这会出现 DHH 第一点里说的那个问题,JS & JSON 加载未完成时首页出不来,但是为此做一个特殊的优化并不是难事
这跟气度和潮流无关,Rails 本质上还是 server side 的解决方案,走的还是服务端处理大部分逻辑的路子。这种情况下某些 ajax 请求返回 html + js partial 确实比直接返回 json 更好。不需要一套 template 分别用 erb 和 js 实现两份(尤其是 template 中含有数据格式化的,更蛋疼)。可以最大程度的重用代码,易于维护,也方便打 cache。
照 SJR 的思路发展,turbolink 的出现就是一种必然了。对不是非常重客户端交互的应用来说,Rails 提供的这种解决方案还是挺好的。提供一定的动态交互的同时最大限度的利用 Rails 在 view 那一层优势。
但如果用前端 MVC 的话,因为 template 的逻辑都在客户端,这时候 json 就是最好的选择。因为 server 已经完全没有必要关心数据怎么展示的问题了。
总之,一件事情,要么前端做,要么后端做。两边都做 50% 的话非常夹生…… 如果两边都做 100% 那就蛋疼了。
#14 楼 @larryzhao 对于 Web 来说,这么多年的发展难道不是将计算能力从 Server 推向 Client? 否则 Chrome 和 V8 为什么要出现?胖客户端的趋势是很明显的啊
我觉得这玩意儿还是不能一概而论,看你做的是啥和当时的情况。
对于一个 Single Page Application, 如果要我不要用 Backbone/Spine/Ember, 而是用 SRJ, 我觉得会做起来困难很多。相应的比如简书的阅读文章的页面,我不可能用前端 MVC 来实现,人家到了这个页面就是想要立刻看到内容的,不现实先载入整个 MVC 框架,完成初始化,再等待 json 回来做内容填充,你就算相应时间再快,用户刷页面还是会看到 html 填充的过程,所以这部分肯定是一个正常的 page render, 相应的所涉及的一些 Ajax, 就很自然的会用到 SRJ.
虽然我用很多 SRJ, 我感情上还是略微倾向于 json + 前端 render, 也可能是因为一些'洁癖'的关系吧,当然 SRJ 可以复用 partial 的好处是极大的,但用下来一直以来的感觉我觉得还是有一些不好的地方的:
文中举例的 create.js.erb
这种写法,我以前也试过,但总觉得不舒服。原因是,本来我的 callback 全部写在 CoffeeScript 中,现在要将 call back 分离写在一个 erb/js 文件中。代码就不集中在一齐了,请问你感觉是如何的?
后面的两种解决方法,感觉都不大好,但能用。
返回 JSON,然后用 JS 的 Template render 就是 DHH 反对的方案 (这个上面已有讨论)
直接在后端 render partial template,然后 JS 获取后在依业务改变页面数据。
还用,如果用 create.js.erb
,部分 JS 就不能 precompile 了,也不能用 CoffeeScript。总觉得 JS, erb 混写很恶心,我的 vim 都认不出来。
#22 楼 @larryzhao 我大概明白你的意思,但是单就 Web 或者叫 B/S 来看,计算能力确实是从 Server 慢慢流向 Client,或者说,B/S 越来越像 C/S
根据不同的团队,不同的需求,可以选择不同的解决方案 大公司大厂,各种人才齐备,api service 一个团队,网站后端一个团队,前端一个团队,完全可以用复杂的方案 对于小团队小项目,人少又要快速开发,可能这种方案比较方便快速。
Basecamp 的绝大多数 Ajax 操作是由服务器端生成并返回 JavaScript (SJR),它的工作流程如下:
这个简单的模式有许多重要的好处。
可以在第一次渲染 model 和后续更新中重复使用同一模板。例如在 Rails 项目中,你可以在这两种情况下都使用 messages/message 这个局部模板。
如果你只返回 JSON, 为了显示 messages 你需要执行两次动作 (一次是服务器端的 first-response, 一次是客户端的 subsequent-updates) —— 除非你正在做一个单页面的 JavaScript 应用程序,甚至第一次响应也是 JSON/client-side 生成并完成的。
使用第 2 种方法可能非常缓慢,因为你不能显示任何东西,直到你的整个 JavaScript 库都加载完毕,然后客户端生成的模板。(这是 Twitter 最初使用的方法,后来抛弃了)。但至少在某些情况这是一个合理的选择,且不需要重复模板。
嵌入 HTML 模板的 JavaScript 的响应时间可能略大于纯 JSON 的响应时间 (当你使用 gzip 压缩时,这种差距通常可以忽略不计)。它不需要客户端的运算来更新。
考虑到这些模板的复杂性和客户端的计算能力,且服务器端生成模板通常可以缓存并在多用户间共享 (参考 Russian Doll caching)。这意味着,从 end-to-end 的角度发送 JavaScript+HTML 有可能比 JSON 配合客户端生成模板更快。
SJR 是一个很容易遵循的执行流程。请求机制是一个标准化的 helper 逻辑,例如 form_for @post, remote: true
。没有必要为每一个请求定义 action 逻辑。控制器执行逻辑,然后渲染部分视图生成响应,以完全相同的方式呈现一个完整的视图,只是模板变成 JavaScript, 而不是 HTML。
完整的例子
0) First-use of the message template.
<h1>All messages:</h1>
<%# renders messages/_message.html.erb %>
<%= render @messages %>
1) Form submitting via Ajax.
<% form_for @project.messages.new, remote: true do |form| %>
...
<%= form.submit "Send message" %>
<% end %>
2) Server creates the model object.
class MessagesController < ActionController::Base
def create
@message = @project.messages.create!(message_params)
respond_to do |format|
format.html { redirect_to @message } # no js fallback
format.js # just renders messages/create.js.erb
end
end
end
3) Server generates a JavaScript response with the HTML embedded.
<%# renders messages/_message.html.erb %>
$('#messages').prepend('<%=j render @message %>');
$('#<%= dom_id @message %>').highlight();
最后一步反应是由 form_for 生成的 XMLHttpRequest-powered 自动处理,视图更新新消息,新消息通过 JS / CSS 动画实现高亮效果。
当我们第一次开始使用 SJR 时,我们使用一种叫 RJS 的编译器,它允许你用 Ruby 编写模板,并转换成 JavaScript。RJS 是一个屌丝版本的 CoffeeScript 你也可以了解一下 Opalr, RJS 让许多人离开了 SJR 模式。
现在我们不再使用 RJS, 但是我们仍然坚定的推荐 SJR。
这并不意味着由服务器端生成 JSON 然后由客户端生成 views 的情况不再需要。如下场景我们会用到这个模式:UI 保真度非常高并且大量的视图状态需要维护。例如日历。当你遇到这个情况时,我们建议使用 Eco template system(think ERB for CoffeeScript)
如果你的 web application 全都是 high-fidelity UI, 它完全合适这种路线。当然自己选择的路跪着也要走完!爷们不哭,站起来撸。但是如果你的程序更像 Basecamp 或者 Github 或者绝大多数基于文档的 web 站点,那么你真的应该使用 SJR。
最后,DHH 大大认为 Russian Doll-caching, Turbolinks 和 SJR。绝对是一个牛 X 的技术组合,能够让你高大强的为 web 应用写出漂亮的代码。颤抖吧凡人。
简单翻译了一下,同学们有发现翻错漏的麻烦 @ 我一下
create.js.erb 仅仅会是很简单的东西
# 找到对应 DOM xxx
# Render partial 准备好需要渲染的内容
# xxx.append(content) 或 xxx.replace(content)
比如 Ruby China 的 app/views/replies/create.js.erb
:
<% if @reply.errors.blank? %>
<% @page = 1 %>
$("#replies .items").append('<%= j(render("reply", :reply => @reply, :reply_counter => @topic.replies_count - 1, :display_edit => true)) %>')
.find(".total b").text('<%= @topic.replies_count %>');
$("#replies .reply a.edit:last").css("display","inline-block");
Topics.replyCallback(1,'<%= j(@msg) %>');
<% else %>
Topics.replyCallback(0,'<%= j(@msg) %>');
<% end %>
以前我也向往过 client side,看过 angular 和 ember 的文档,但是真正动手的时候就怂了,将渲染移到 client side,意味着原本每个小查询都要写成 API,例如分类列表。而要减少远程调用,加快第一页速度,就要写 init 逻辑,把要用的数据塞到页面 js。最后为了 seo,还要单独写一份简化版的服务端渲染(discourse.org)。一般的 CRUD 用 client side 方案真是麻烦很多。
客户端的计算能力提升,可以用去需要客户端做的事上,例如拍照、定位、动态图表、编辑器等等,而要给移动应用写 API,可以单独写 API 接口,不跟网站混在一起。
所以,我赞成 DHH 的方案。
其实嘛,我觉得,网站用 dhh 这种,web app 用 js mvc 就好,但是问题就是,一般都是先有个站,最后才衍生出 app,所以就蛋疼的最后加个 api 目录搞 app.... 哎。。。
目前越来越多的项目都是 restful 后台,然后 web 端弱化成一个普通的 client 端. 这样做规模越大越得力,数据和逻辑都是后台,前台负责本地化展现,api 级别是 restful 的,利于扩展。
对于 native app,这么做貌似阻力不大,对于 web 的话,能熟练使用 js 并做测试的人实在太少了,所以接受的难度肯定要大不少。而且 SEO 之类的问题也比较头疼。
我个人是赞同将计算力向客户端迁移的,当然前提是应用要足够成功,越成功的应用,越多计算放在客户端,则需要的服务器维护费用会越少,当用户量超过 10w 以上的时候,差距应该会越来越明显。善用 browser cache, server side cache 等等也是减少 cpu,io 量的一个办法,但是都不如仅仅拿 JSON 那么节省 bandwidth 和计算力。
@xds2000 RJS 是很早以前(Rails 3 以前)Rails 推出的用 Ruby 写 JavaScript 的方法,完全是 Ruby 代码
举个栗子:
# some_view.rjs
page['time_updated'].replace_html Time.now
page.visual_effect :highlight, 'the_list'
这段代码是从 这篇 ITEye blog 上摘抄的,看看日期你就知道有多古老了。
Rails 会把这一段代码渲染成 JavaScript,当时还是使用的 Prototype。年代太久远记不清楚了,大概是这个样子:
$('time_update').replace('some time');
$('the_list').visualEffect('highlight');
这种技术非常依赖前端的 JavaScript 库,而且也不够直观。只不过算是一种“用 Ruby 写任何东西”的理念的衍生物。
后来在 Yehuda 把 RJS 移除了(貌似是 Rails 3 时代?),换成了 js.erb 的方式,直接在 view 里面写 JavaScript。这就是 DHH 文章里面说的 SJR(Server generated JavaScript Responses)。
#58 楼 @darkbaby123 原来是 Yehuda 把 RJS 移除的,估计他也是觉得现在的 SJR 还是太蹩脚,然后直接投身到 Ember 的开发了
how about this https://github.com/rails-api/rails-api
如果是一个本身就是前端出身的人肯定给出的是完全不一样的看法。
sjr 在我看来无法前后端分工开发,两种语言混写出错率高。
普通网站带上少量 ajax 可以用 sjr,真正的 web app 还是得看 angular,ember,knockout 之类的
@small_fish__ 反正我现在是没用的,就是 Rails + Ember。因为我还是用的 Devise 的 session 验证。虽然有办法去跟 Rails API 结合,但我还需要 sprocket,懒得折腾了。 其实 Rails API 就是个减去了不必要 middleware 的 Rails,其他地方没什么区别。
@small_fish__ BTW 作为 Rails API 依赖的一部分,active_model_serializers 貌似也是有 Yehuda 参与的。它默认生成的 json 格式和 Ember Data 是吻合的,不过现在有点不一样了。
好处 #1: 重用模板且不牺牲性能 Angular 可以重用 Rails 的 局部模板,任何后缀都可以
不能显示任何东西,直到你的整个 JavaScript 库都加载完毕 Angular 可以先显示 html 模板,局部用 loading,让用户先看到页面。
你需要执行两次动作 (一次是服务器端的 first-response, 一次是客户端的 subsequent-updates) 如果是简单页面,一次执行即可,ng-init 可以接受初始化数据 如果是复杂的,有重用模板的页面,可以用传统 Rails 的方式重用模板。
服务器端生成模板通常可以缓存并在多用户间共享 DHH 说的是页面片断缓存,而 模板缓存 + 数据缓存 一样解决问题,而且不混乱。
从 end-to-end 的角度发送 JavaScript+HTML 有可能比 JSON 配合客户端生成模板更快。 客户端生成模块?有时候根本不需要重新生成模板,DHH 真的应该看看 ng-repeat 如果是调用新模板, 两次 Ajax 请求 + 客户端计算 VS 一次 Ajax 请求 + 服务器端计算 前者服务器性能要求更低,后者少一次请求数,但是这是在用户交互时,相差约等于 0
浅显易懂的执行流程 老式 Form action 是否满足 多功能按钮执行的需求?