我将通过列出 Ember 和 Rails 之间的一些主要技术差异,在 StackOverflow 的精神内尽力回答这个问题。我将在programmers.stackexchange.com上将更哲学的一面留给其他人。
如果可以帮助您可视化所有内容如何组合在一起,您可以在工作的 jsFiddle中找到下面的所有代码示例。
集合和对象的单独路由
Ember 和 Rails 之间的一个主要区别是集合路由(管理对象列表)和项目路由(管理单个对象)之间的关系。在 Rails 中,这些都由单个资源控制器处理。在 Ember 中,这些通常由两个单独的路由处理,因为它们操纵两种不同的数据结构:
App.Router.map(function () {
this.route("posts", { path: "posts" });
this.route("post", { path: "post/:post_id" });
});
App.PostsRoute = Ember.Route.extend({
model: function (params) {
return App.Post.find();
}
});
App.PostRoute = Ember.Route.extend({
model: function (params) {
return App.Post.find(params.post_id);
}
});
路由 vs. 控制器 vs. 视图 vs. 模板
在 Rails 中,您的代码分为三大类:
- 模型:对数据库行的面向对象抽象。
- 视图:可由控制器呈现的模板。
- 控制器:接受 HTTP 请求、加载和操作模型、渲染视图。
在 Ember 中,职责的划分有很大不同。
楷模。Ember 模型的工作方式与 Rails 模型非常相似。
App.Post = DS.Model.extend({
title: DS.attr("string"),
body: DS.attr("string"),
comments: DS.hasMany("App.Comment")
});
路线。路由表示您的应用程序中用户可见的位置,它们对应于 URL,例如/post/7
或/about
。正如您在上面的代码示例中看到的,路由在 Ember 中做了更多的事情。最重要的是,他们查找与给定 URL 对应的模型。他们还负责连接适当的控制器和视图,稍后您将看到。
控制器。控制器与 Rails 完全不同!关于 Ember 控制器需要理解的两个最重要的事情是:(1)它们基本上是模型对象周围的智能代理,(2)它们通常是单例的。因此,您将只有一个PostController
将连接到您现在正在查看的任何帖子。
一般来说,您使用 Ember 控制器来管理不属于 URL 或数据库的瞬态状态。这是一个例子:
App.PostController = Ember.ObjectController.extend({
// This shared between all posts for as long as the app runs (because
// this controller is a singleton), but it doesn't get saved in the database
// (because this is a controller, not a model).
lowRatedCommentsShown: false,
// Called by PostView or its template in response to an HTML event.
showLowRatedComments: function () {
this.set("lowRatedCommentsShown", true);
},
// Called by PostView or its template in response to an HTML event.
hideLowRatedComments: function () {
this.set("lowRatedCommentsShown", false);
}
});
因为 Ember 控制器是模型的代理,它们也倾向于积累几乎属于模型的逻辑,但感觉与应用程序中的特定屏幕联系得太紧密了。
视图和模板。Ember 视图和模板一起工作。最好将它们视为 GUI 小部件。
App.PostView = Ember.View.extend({
// This can be omitted when we're created by a route.
templateName: 'post'
// Any HTML event handlers would go here if we needed them. Our job is to
// map between HTML events and events understood by the controller.
//doubleClick: function (evt) {
// // We'll actually bind this to a specific button, not a click event.
// this.get("controller").send("showLowRatedComments");
//}
});
我们的帖子模板自由地混合了模型定义的字段和控制器定义的字段:
<script type="text/x-handlebars" data-template-name="post">
<h2>{{title}}</h2>
<div class="body">{{body}}</div>
{{#if lowRatedCommentsShown}}
<button {{action 'hideLowRatedComments'}}>Hide Low-Rated Comments</button>
{{else}}
<button {{action 'showLowRatedComments'}}>Show Low-Rated Comments</button>
{{/if}}
{{partial "comments"}}
</script>
请注意,随着我们模型或控制器上的字段发生变化,视图将自动重新呈现 HTML 中需要更新的那些部分!
异步行为、计算属性和绑定
因为 Ember.js 在浏览器中运行,所以很多操作都是异步的。Ember 的许多基本设计都是基于使异步更新变得愉快和容易。这种情况的一个关键后果是对象异步加载。当你调用 时find
,你会得到一个卸载的对象:
post = App.Post.find(params.post_id)
post.get("isLoaded"); // -> false
post.get("title"); // -> not yet available
当服务器向您发送数据时,您将看到:
post.get("isLoaded"); // -> true
post.get("title"); // -> "Post #1"
为了让这一切变得轻松,Ember 严重依赖于计算属性、观察者和绑定。在每种情况下,关键思想是数据的更改应该自动在系统中传播。例如,我们可以使用计算属性来确保在评论更改isLowRated
时随时更新:rating
App.Comment = DS.Model.extend({
post: DS.belongsTo("App.Post"),
body: DS.attr("string"),
rating: DS.attr("number"),
isLowRated: function () {
return this.get("rating") < 2;
}.property("rating") // A list of properties we depend on.
});
请注意,Ember 的 Handlebars 模板与该系统深度集成。当您{{title}}
在模板中编写代码时,您会建立一个绑定,该绑定将在 DOM 发生更改时自动更新title
。在编辑字段的情况下,此绑定在两个方向都有效!对显示值的更改将直接推送回模型(尽管事务可能用于回滚)。
还值得记住的是,许多动态更新(尤其是绑定)在当前“运行循环”结束时异步运行。Ember.run
这就是为什么你会经常在测试套件中看到调用:
Ember.run(function () {
// Change some bindings here. Not all changes will propagate immediately.
});
// Test that the values have all propagated here, after the run loop is done.
在实践中,Ember 感觉比 Rails 异步得多,但不如 Node.js 这样的事件系统异步。这是因为大多数异步更新都是由绑定自动管理的。
幸存的 Ember 数据
这是我将偏离严格的技术细节并提及一些实用建议的地方。Ember Data 提供DS.Model
,见上。它不是 Ember.js 的唯一模型层——查看ember-rest、ember-resource和类似库以获取替代方案。目前,Ember Data 还没有正式发布,但如果您喜欢生活在最前沿,可以非常谨慎地在生产应用程序中使用它。一些技巧:
- 几个主要的弱点包括验证、延迟加载和多个打开的事务。在提交 Ember Data 之前,请编写几个小型测试程序以确保它可以真正完成您需要它做的事情。
- 不要与 RESTAdapter 争吵。准确地提供它想要的 JSON,即使这意味着构建一个代理。特别是,目前这意味着
hasMany
在序列化对象时为 Rails 中的每个关系序列化一个完整的 ID 列表。如果您使用的是 Rails,请参阅active_model_serializers 。
- 不要太专注于特定的设计。相反,要准备好偶尔绕过限制并做出妥协。
使用 Ember Data 可以获得非常好的结果。但它远不如 ActiveModel 成熟,因此需要这样对待。