Backbone.js 应用完全在 JS 中呈现

Backbone.js app completely rendered in JS

我正在尝试在 Backbone.js 中构建一个新项目,我的背景是在 Java、PHP、Rails、Perl 和很快。我已经完成了相当多的 JS 工作,包括 JQuery 和一些 Node,但我在思考在 Backbone 中构建整个应用程序的最佳实践时遇到了一些麻烦。教程中的内容似乎与我看到的实际应用程序的实施方式有所不同。

我一直在读 Addy Osmani 的 Backbone Fundamentals。他的示例让您创建一个静态 HTML 文件,将应用程序的框架放入其中,然后由 JS 修改。示例片段:

  <section id="todoapp">
    <header id="header">
      <h1>todos</h1>
      <input id="new-todo" placeholder="What needs to be done?" autofocus>
    </header>
    <section id="main">
      <input id="toggle-all" type="checkbox">
      <label for="toggle-all">Mark all as complete</label>
      <ul id="todo-list"></ul>
    </section>
    <footer id="footer"></footer>
  </section>

这一切都很好,而且似乎是一种足够体面的方法。但我在野外见过几个应用程序,其中静态 HTML 文件只是少数样式表和脚本标签。并且看起来整个应用程序都是由 JS 生成的。这对于具有大量动态内容不同视图的应用程序似乎是有益的。

我真的很想了解这些应用程序是如何构建的,但我一直无法找到涵盖它的文档或教程。我想我可以一起破解一些东西,但我真的更想了解正确的方法、最佳实践等。

有人可以指出一些文档的方向以了解这一点吗?

很好的问题,但不幸的是相当开放。有 many such tutorials around,但我宁愿通过向您简要概述需要完成的工作来回答。

您的问题所指的区别是 HTML 的渲染发生的地方:全部在服务器端,全部在客户端,或者混合

另一个(相关)问题是哪里 路由发生。

最接近 "all client side" 的方法是有一个服务器端面向用户的 URL 端点,returns 一个小的 HTML 响应是本质上是客户端的引导程序,如本例所示:

GET /

<html>
<head>
    <title>Loading</title>
    <script data-main="/client/main" src="/client/libs/require.js"></script>
</head>
<body>
</body>
</html>

这使用了 AMD 模块加载器 require.js(还有许多其他选择,但请耐心等待)。反过来,require.js 将自动加载 data-main 中指定的脚本,在这种情况下,/client/main.js(约定不包括 .js)。

GET /client/main.js

require.config({
    baseUrl: '/client',       // this tells require to load things relative to this "base" path
    paths: {
         underscore: 'libs/underscore',
         jquery: 'libs/jquery',
         backbone: 'libs/backbone',
         text: 'libs/require-plugins/text',      // this is a "plugin" for require.js that allows you to load textfiles instead of scripts when you precede the path with 'text!'
         // etc for other libs
    },
    shim: {
         underscore: { exports: '_' },
         jquery: { exports: '$' },
         backbone: { deps: ['underscore', 'jquery'], exports: 'Backbone' }
    }
});

require(['jquery', 'application'], function($, Application) {

    var $rootDiv = $("<div>", {id: "app-root", class: ""});
    $('body').prepend($rootDiv);
    var app = new Application({ rootEl: $rootDiv });
    app.start();

});

我不想具体地深入研究 require.js,所以我只注意到它的功能与它看起来的一样:它以异步和受控的方式加载 JavaScript时尚(所以你说 this 脚本取决于 that 脚本等)。第一个块只是 require.js 的配置,有点离题,但我想让它变得现实。

第二块更有趣。内容如下:

  1. require 以下依赖项:jqueryapplication
  2. 加载它们后(从 HTTP GET 加载或从缓存加载,如果之前已加载),分别将它们别名为 $Application
  3. 创建一个新的 div 并将其添加到 body
  4. 创建一个新的 Application,指定 rootEl 作为 div 创建的
  5. 在应用程序实例上调用 start

这是 DOM 此时的样子:

<html>
<head>
    <title>Loading</title>
    <script data-main="/client/main" src="/client/libs/require.js"</script>
    <script data-requiremodule="main" src="/client/main.js"></script>
    <script data-requiremodule="jquery" src="/client/libs/jquery.js"></script>
     <!-- ... etc ... -->
     <script data-requiremodule="application" src="/client/application.js"></script>
</head>
<body>
     <div id="application-root"></div>
</body>
</html>

现在关于最后一个依赖项,application.js:

GET /client/application.js

define(['underscore', 'jquery', 'backbone', 'text!templates.html'], function(_, $, Backbone, Templates) {

    var getStartOptions = function(options) {
        options = options || {};
        _(options).defaults({
            rootEl: $('body'),
            initialRoute: '/'
        });

        return options;
    };

    return Backbone.View.extend({
        initialize: function(options) {
            this.state = new Backbone.Model(getStartOptions(options));

            this.listenToOnce(this.state, 'change:started', function() {
                this.state.set(getStartOptions(options));
            }, this);

            this.listenTo(this.state, 'change:rootEl', this.onChangeRootEl, this);
        },
        onChangeRootEl: function(val) {
            this.setElement(val);
            if (!this.state.previous('rootEl')) {
                var $templates = $('<div>', {id: 'app-templates'});
                $templates.html(Templates);
                $('body').append($templates);
            }
            this.render();
        },
        template: _.template($('#app-templates #app-layout-template').html()),
        render: function() {
            this.$el.html(this.template());
            return this;
        },
        start: function() {
            this.state.set('started', true);
        }
    });

});

define 函数类似于 require(它是 require.js 的一部分)但它本身不是 "run" - require 是。所以,无论你 define 你必须 require 在其他地方 运行.

define 函数参数的 return 值是您 require 得到的值。

在这种情况下,getStartOptionsprivate 但返回的 View 是之前要求 ['application'] 的值。

大致情况如下:

  1. 之前,我们实例化这里定义的View(我们称之为Application
  2. 我们叫start
  3. startstarted 设置为 true
  4. started 为真时,视图(通过 listenToOnce 调用)设置初始选项。
  5. 第一次设置 rootEl 时,我们将模板注入 DOM
  6. When options change, we update the view accordingly and then re-render.

所有这一切之后,结果将是:

<body>
    <div id="app-root">
        ... content of the template with id "app-layout-template" ...
    </div>
</body>

布局模板只需要是这种形式即可。

GET /client/templates.html

<script type="text/whatever-you-want" id="app-layout-template">
...
</script>

<script type="text/x-underscore-template" id="example-of-app-layout-template">
    <header><h2><%= appTitle %></h2></header>
    <section><%- appContent %></section>
    <footer><p><%= appFooterMessage %></footer>
</script>

Here is some information about underscore templates. 还有很多其他选择。