8

虽然我最近的大部分工作主要是使用 Ruby on Rails 和大量的 Javascript(主要是 jQuery),但我想构建一个单页应用程序并意识到 Ember.js 似乎是一个新兴的流行框架用于接近此类应用程序。

从各种文档和教程来源来看,Ember.js 似乎需要一种与 Ruby on Rails 或其他典型服务器端框架截然不同的思考方式来解决问题。使用诸如 Ruby on Rails 之类的框架随着时间的推移而开发的关于“事情应该如何工作”的某些假设甚至可能会妨碍真正理解和接受“Ember 方式”。

Ruby on Rails 开发人员在尝试学习 Ember 时需要消除哪些先入为主的观念?Ruby on Rails 开发人员应该尝试思考的最具创新性和最重要的 Ember 概念是什么?

提前致谢!

4

1 回答 1

13

我将通过列出 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-restember-resource和类似库以获取替代方案。目前,Ember Data 还没有正式发布,但如果您喜欢生活在最前沿,可以非常谨慎地在生产应用程序中使用它。一些技巧:

  1. 几个主要的弱点包括验证、延迟加载和多个打开的事务。在提交 Ember Data 之前,请编写几个小型测试程序以确保它可以真正完成您需要它做的事情。
  2. 不要与 RESTAdapter 争吵。准确地提供它想要的 JSON,即使这意味着构建一个代理。特别是,目前这意味着hasMany在序列化对象时为 Rails 中的每个关系序列化一个完整的 ID 列表。如果您使用的是 Rails,请参阅active_model_serializers 。
  3. 不要太专注于特定的设计。相反,要准备好偶尔绕过限制并做出妥协。

使用 Ember Data 可以获得非常好的结果。但它远不如 ActiveModel 成熟,因此需要这样对待。

于 2013-04-10T14:47:04.997 回答