在开始Ember.js时,Rails或服务器端开发人员应该在思考中转变什么范式?

时间:2021-07-12 16:16:55

While most of my recent work has primarily been with Ruby on Rails and a liberal dose of Javascript (mainly jQuery), I would like to build a single page application and realize that Ember.js seems to be an up-and-coming popular framework for approaching such apps.

虽然我最近的大部分工作主要是使用Ruby on Rails和大量的Javascript(主要是jQuery),但我想建立一个单页面应用程序,并意识到Ember.js似乎是一个崭露头角的流行框架接近这些应用程序。

From various sources of documentation and tutorials, it seems that Ember.js requires a very different way of thinking about how to solve problems than Ruby on Rails or other typical server-side frameworks. It seems possible that certain assumptions about 'the way things should work' that one develops over time using a framework such as Ruby on Rails may even get in the way of truly understanding and embracing the 'Ember Way.'

从文档和教程的各种来源来看,似乎Ember.js需要一种与Ruby on Rails或其他典型服务器端框架相比如何解决问题的完全不同的思考方式。似乎有可能使用像Ruby on Rails这样的框架随着时间的推移开发的“事物应该如何运作”的某些假设甚至可能妨碍真正理解并接受“Ember Way”。

What preconceived notions should a Ruby on Rails developer need to eliminate when trying to learn Ember? And what are the most innovative and important Ember concepts that a Ruby on Rails developer should try to wrap his/her mind around?

在尝试学习Ember时,Ruby on Rails开发人员需要消除哪些先入为主的观念? Ruby on Rails开发人员应该尝试将他/她的思想包裹起来,最具创新性和最重要的Ember概念是什么?

Thanks in advance!

提前致谢!

1 个解决方案

#1


13  

I'm going to do my best to answer this question within the spirit of * by listing some major technical differences between Ember and Rails. I'll leave the more philosophical side for somebody else over at programmers.stackexchange.com.

我将尽力通过列出Ember和Rails之间的一些主要技术差异,在*的精神内回答这个问题。我将在programmers.stackexchange.com上为其他人留下更多的哲学方面。

You can find all the code examples below in a working jsFiddle if that helps you visualize how everything fits together.

您可以在工作的jsFiddle中找到下面的所有代码示例,如果这有助于您可视化所有内容的组合方式。

Separate routes for collections and objects

One major difference between Ember and Rails is the relation between collection routes (which manage a list of objects) and item routes (which manage a single object). In Rails, these are both handled by a single resource controller. In Ember, these are generally handled by two separate routes, because they manipulate two different data structures:

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);  
  }
});

Routes vs. Controllers vs. Views vs. Templates

In Rails, your code is split between three major groups of classes:

在Rails中,您的代码分为三大类:

  • Models: An object-oriented abstraction over a database row.
  • 模型:数据库行上面向对象的抽象。

  • Views: Templates that can be rendered by a controller.
  • 视图:可由控制器呈现的模板。

  • Controllers: Accept HTTP requests, load and manipulate models, render views.
  • 控制器:接受HTTP请求,加载和操作模型,渲染视图。

In Ember, the breakdown of responsibilities is considerably different.

在恩伯,责任的细分是截然不同的。

Models. Ember models work much like Rails models.

楷模。 Ember模型的工作方式与Rails模型非常相似。

App.Post = DS.Model.extend({
  title: DS.attr("string"),
  body: DS.attr("string"),
  comments: DS.hasMany("App.Comment")
});

Routes. Routes represent user-visible locations within your app, and they correspond to URLs like /post/7 or /about. As you can see in the code examples above, routes do a lot more in Ember. Most importantly, they look up the models corresponding to a given URL. They're also in charge of hooking up appropriate controllers and views, as you'll see in a second.

路线。路线表示应用中用户可见的位置,它们对应于/ post / 7或/ about等网址。正如您在上面的代码示例中所看到的,路由在Ember中做了很多。最重要的是,他们查找与给定URL对应的模型。正如您将在一秒钟内看到的那样,他们还负责连接适当的控制器和视图。

Controllers. Controllers are nothing like Rails! The two most important things to understand about Ember controllers are that: (1) they're basically smart proxies around model objects, and (2) they're normally singletons. So you'll only have one PostController which will be wired up to whichever post you're looking at right now.

控制器。控制器与Rails完全不同!关于Ember控制器的两个最重要的事情是:(1)它们基本上是模型对象周围的智能代理,(2)它们通常是单例。因此,您只需要一个PostController,它将连接到您正在查看的任何帖子。

Generally speaking, you use Ember controllers to manage transient state that doesn't belong in the URL or in the database. Here's an example:

一般来说,您使用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);
  } 
});

Because Ember controllers are proxies around models, they also tend to accumulate logic that almost belongs in the model, but feels too closely tied to a specific screen in your app.

因为Ember控制器是模型周围的代理,它们也倾向于积累几乎属于模型的逻辑,但感觉与应用程序中的特定屏幕紧密相关。

Views and templates. Ember views and templates work together. It's best to think of them as a GUI widget.

视图和模板。 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");
  //}
});

Our post template freely mixes fields defined by the model and fields defined by the controller:

我们的帖子模板可以*混合模型定义的字段和控制器定义的字段:

<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>

Note that as fields on our model or controller change, the view will automatically rerender just those portions of the HTML which need to be updated!

请注意,随着我们的模型或控制器上的字段发生变化,视图将自动重新呈现需要更新的HTML部分!

Asynchronous behavior, computed properties, and bindings

Because Ember.js runs in the browser, many operations are asynchronous. Much of Ember's fundamental design is based on making asynchronous updates pleasant and easy. One key piece consequence of this situation is that objects load asynchronously. When you call find, you'll get back an unloaded object:

因为Ember.js在浏览器中运行,所以许多操作都是异步的。 Ember的许多基本设计都基于使异步更新变得轻松愉快。这种情况的一个关键因素是对象异步加载。当你调用find时,你将获得一个卸载的对象:

post = App.Post.find(params.post_id)
post.get("isLoaded"); // -> false
post.get("title");    // -> not yet available

When the server sends you the data, you'll see:

当服务器向您发送数据时,您将看到:

post.get("isLoaded"); // -> true
post.get("title");    // -> "Post #1"

To help make this painless, Ember relies heavily on computed properties, observers and bindings. In each of these cases, the key idea is that changes to data should automatically ripple through the system. For example, we can use a computed property to ensure that isLowRated is updated any time that a comment's rating changes:

为了帮助实现这一目标,Ember非常依赖于计算属性,观察者和绑定。在每种情况下,关键的想法是数据的更改应该自动波及整个系统。例如,我们可以使用computed属性来确保在评论的评级发生变化时更新isLowRated:

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.
});

Note that Ember's Handlebars templates are deeply integrated with this system. When you write {{title}} in a template, you establish a binding that will automatically update the DOM whenever title changes. In the case of edit fields, this binding works in both directions! Changes to a displayed value will be pushed straight back to the model (though transactions may be used to roll it back).

请注意,Ember的Handlebars模板与此系统深度集成。当您在模板中编写{{title}}时,您将建立一个绑定,以便在标题更改时自动更新DOM。在编辑字段的情况下,此绑定在两个方向上都有效!对显示值的更改将直接推送回模型(尽管可以使用事务将其回滚)。

It's also worth remembering that many of the dynamic updates—particularly bindings—run asynchronously at the end of the current "run loop". This is why you'll often see calls to Ember.run in test suites:

还值得记住的是,许多动态更新 - 尤其是绑定 - 在当前“运行循环”结束时异步运行。这就是为什么你经常会在测试套件中看到对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.

In practice, Ember feels much more asynchronous than Rails, but less asynchronous than an evented system like Node.js. This is because most asynchronous updates are managed automatically by bindings.

在实践中,Ember感觉比Rails更异步,但是比Node.js这样的事件系统更少异步。这是因为大多数异步更新都是通过绑定自动管理的。

Surviving Ember Data

This is the one place I'm going to stray from strictly technical details and mention some practical advice. Ember Data provides DS.Model, seen above. It's not the only model layer for Ember.js—check out ember-rest, ember-resource and similar libraries for alternatives. At this point in time, there's no official release of Ember Data, but it can be used very cautiously in production apps if you like to live on the bleeding edge. Some tips:

这是我将要从严格的技术细节中偏离并提及一些实用建议的地方。 Ember Data提供了DS.Model,如上所示。它不是Ember.js的唯一模型层 - 检查ember-rest,ember-resource和类似的替代库。目前还没有正式发布的Ember Data,但如果你想生活在最前沿的话,它可以在生产应用中非常谨慎地使用。一些技巧:

  1. Several major weak spots include validations, lazy loading and multiple open transactions. Before committing to Ember Data, write several small test programs to make sure it can actually do what you need it to do.
  2. 几个主要的弱点包括验证,延迟加载和多个开放交易。在提交Ember Data之前,写几个小的测试程序,以确保它实际上可以做你需要它做的事情。

  3. Don't pick fights with RESTAdapter. Feed it exactly the JSON it wants, even if that means building a proxy. In particular, this currently means serializing a complete list of IDs for every hasMany relationship in Rails when serializing an object. See active_model_serializers if you're using Rails.
  4. 不要选择使用RESTAdapter进行战斗。准确地输入它想要的JSON,即使这意味着构建代理。特别是,这当前意味着在序列化对象时为Rails中的每个hasMany关系序列化完整的ID列表。如果您正在使用Rails,请参阅active_model_serializers。

  5. Don't get too fixated on a specific design. Instead, be prepared to occasionally work around limitations and make compromises.
  6. 不要过于关注特定的设计。相反,要做好准备偶尔解决限制并做出妥协。

It's possible to get very good results with Ember Data. But it's vastly less mature than ActiveModel, and it needs to be treated as such.

使用Ember Data可以获得非常好的结果。但它远不如ActiveModel成熟,需要对其进行处理。

#1


13  

I'm going to do my best to answer this question within the spirit of * by listing some major technical differences between Ember and Rails. I'll leave the more philosophical side for somebody else over at programmers.stackexchange.com.

我将尽力通过列出Ember和Rails之间的一些主要技术差异,在*的精神内回答这个问题。我将在programmers.stackexchange.com上为其他人留下更多的哲学方面。

You can find all the code examples below in a working jsFiddle if that helps you visualize how everything fits together.

您可以在工作的jsFiddle中找到下面的所有代码示例,如果这有助于您可视化所有内容的组合方式。

Separate routes for collections and objects

One major difference between Ember and Rails is the relation between collection routes (which manage a list of objects) and item routes (which manage a single object). In Rails, these are both handled by a single resource controller. In Ember, these are generally handled by two separate routes, because they manipulate two different data structures:

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);  
  }
});

Routes vs. Controllers vs. Views vs. Templates

In Rails, your code is split between three major groups of classes:

在Rails中,您的代码分为三大类:

  • Models: An object-oriented abstraction over a database row.
  • 模型:数据库行上面向对象的抽象。

  • Views: Templates that can be rendered by a controller.
  • 视图:可由控制器呈现的模板。

  • Controllers: Accept HTTP requests, load and manipulate models, render views.
  • 控制器:接受HTTP请求,加载和操作模型,渲染视图。

In Ember, the breakdown of responsibilities is considerably different.

在恩伯,责任的细分是截然不同的。

Models. Ember models work much like Rails models.

楷模。 Ember模型的工作方式与Rails模型非常相似。

App.Post = DS.Model.extend({
  title: DS.attr("string"),
  body: DS.attr("string"),
  comments: DS.hasMany("App.Comment")
});

Routes. Routes represent user-visible locations within your app, and they correspond to URLs like /post/7 or /about. As you can see in the code examples above, routes do a lot more in Ember. Most importantly, they look up the models corresponding to a given URL. They're also in charge of hooking up appropriate controllers and views, as you'll see in a second.

路线。路线表示应用中用户可见的位置,它们对应于/ post / 7或/ about等网址。正如您在上面的代码示例中所看到的,路由在Ember中做了很多。最重要的是,他们查找与给定URL对应的模型。正如您将在一秒钟内看到的那样,他们还负责连接适当的控制器和视图。

Controllers. Controllers are nothing like Rails! The two most important things to understand about Ember controllers are that: (1) they're basically smart proxies around model objects, and (2) they're normally singletons. So you'll only have one PostController which will be wired up to whichever post you're looking at right now.

控制器。控制器与Rails完全不同!关于Ember控制器的两个最重要的事情是:(1)它们基本上是模型对象周围的智能代理,(2)它们通常是单例。因此,您只需要一个PostController,它将连接到您正在查看的任何帖子。

Generally speaking, you use Ember controllers to manage transient state that doesn't belong in the URL or in the database. Here's an example:

一般来说,您使用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);
  } 
});

Because Ember controllers are proxies around models, they also tend to accumulate logic that almost belongs in the model, but feels too closely tied to a specific screen in your app.

因为Ember控制器是模型周围的代理,它们也倾向于积累几乎属于模型的逻辑,但感觉与应用程序中的特定屏幕紧密相关。

Views and templates. Ember views and templates work together. It's best to think of them as a GUI widget.

视图和模板。 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");
  //}
});

Our post template freely mixes fields defined by the model and fields defined by the controller:

我们的帖子模板可以*混合模型定义的字段和控制器定义的字段:

<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>

Note that as fields on our model or controller change, the view will automatically rerender just those portions of the HTML which need to be updated!

请注意,随着我们的模型或控制器上的字段发生变化,视图将自动重新呈现需要更新的HTML部分!

Asynchronous behavior, computed properties, and bindings

Because Ember.js runs in the browser, many operations are asynchronous. Much of Ember's fundamental design is based on making asynchronous updates pleasant and easy. One key piece consequence of this situation is that objects load asynchronously. When you call find, you'll get back an unloaded object:

因为Ember.js在浏览器中运行,所以许多操作都是异步的。 Ember的许多基本设计都基于使异步更新变得轻松愉快。这种情况的一个关键因素是对象异步加载。当你调用find时,你将获得一个卸载的对象:

post = App.Post.find(params.post_id)
post.get("isLoaded"); // -> false
post.get("title");    // -> not yet available

When the server sends you the data, you'll see:

当服务器向您发送数据时,您将看到:

post.get("isLoaded"); // -> true
post.get("title");    // -> "Post #1"

To help make this painless, Ember relies heavily on computed properties, observers and bindings. In each of these cases, the key idea is that changes to data should automatically ripple through the system. For example, we can use a computed property to ensure that isLowRated is updated any time that a comment's rating changes:

为了帮助实现这一目标,Ember非常依赖于计算属性,观察者和绑定。在每种情况下,关键的想法是数据的更改应该自动波及整个系统。例如,我们可以使用computed属性来确保在评论的评级发生变化时更新isLowRated:

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.
});

Note that Ember's Handlebars templates are deeply integrated with this system. When you write {{title}} in a template, you establish a binding that will automatically update the DOM whenever title changes. In the case of edit fields, this binding works in both directions! Changes to a displayed value will be pushed straight back to the model (though transactions may be used to roll it back).

请注意,Ember的Handlebars模板与此系统深度集成。当您在模板中编写{{title}}时,您将建立一个绑定,以便在标题更改时自动更新DOM。在编辑字段的情况下,此绑定在两个方向上都有效!对显示值的更改将直接推送回模型(尽管可以使用事务将其回滚)。

It's also worth remembering that many of the dynamic updates—particularly bindings—run asynchronously at the end of the current "run loop". This is why you'll often see calls to Ember.run in test suites:

还值得记住的是,许多动态更新 - 尤其是绑定 - 在当前“运行循环”结束时异步运行。这就是为什么你经常会在测试套件中看到对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.

In practice, Ember feels much more asynchronous than Rails, but less asynchronous than an evented system like Node.js. This is because most asynchronous updates are managed automatically by bindings.

在实践中,Ember感觉比Rails更异步,但是比Node.js这样的事件系统更少异步。这是因为大多数异步更新都是通过绑定自动管理的。

Surviving Ember Data

This is the one place I'm going to stray from strictly technical details and mention some practical advice. Ember Data provides DS.Model, seen above. It's not the only model layer for Ember.js—check out ember-rest, ember-resource and similar libraries for alternatives. At this point in time, there's no official release of Ember Data, but it can be used very cautiously in production apps if you like to live on the bleeding edge. Some tips:

这是我将要从严格的技术细节中偏离并提及一些实用建议的地方。 Ember Data提供了DS.Model,如上所示。它不是Ember.js的唯一模型层 - 检查ember-rest,ember-resource和类似的替代库。目前还没有正式发布的Ember Data,但如果你想生活在最前沿的话,它可以在生产应用中非常谨慎地使用。一些技巧:

  1. Several major weak spots include validations, lazy loading and multiple open transactions. Before committing to Ember Data, write several small test programs to make sure it can actually do what you need it to do.
  2. 几个主要的弱点包括验证,延迟加载和多个开放交易。在提交Ember Data之前,写几个小的测试程序,以确保它实际上可以做你需要它做的事情。

  3. Don't pick fights with RESTAdapter. Feed it exactly the JSON it wants, even if that means building a proxy. In particular, this currently means serializing a complete list of IDs for every hasMany relationship in Rails when serializing an object. See active_model_serializers if you're using Rails.
  4. 不要选择使用RESTAdapter进行战斗。准确地输入它想要的JSON,即使这意味着构建代理。特别是,这当前意味着在序列化对象时为Rails中的每个hasMany关系序列化完整的ID列表。如果您正在使用Rails,请参阅active_model_serializers。

  5. Don't get too fixated on a specific design. Instead, be prepared to occasionally work around limitations and make compromises.
  6. 不要过于关注特定的设计。相反,要做好准备偶尔解决限制并做出妥协。

It's possible to get very good results with Ember Data. But it's vastly less mature than ActiveModel, and it needs to be treated as such.

使用Ember Data可以获得非常好的结果。但它远不如ActiveModel成熟,需要对其进行处理。