很好的问题,但不幸的是相当开放。有manysuchtutorialsaround,但我宁愿给你一个需要做什么的高级概述来回答。
您的问题所指的区别是在哪里呈现 HTML:全部在服务器端,全部在客户端,或两者兼而有之。
另一个(相关)问题是在哪里 路由发生。
最接近“所有客户端”的方法是有一个服务器端面向用户的 URL 端点,该端点返回一个小的 HTML 响应,该响应本质上是客户端的引导程序,如下例所示:
获取/
<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 的配置,有点离题,但我想让它变得现实。
第二块更有趣。它是这样说的:
-
require 以下依赖项:jquery 和 application
- 在加载它们之后(从
HTTP GET 或者如果之前加载则从缓存中),将它们分别别名为$ 和Application。
- 创建一个新的
div 并将其添加到body。
- 创建一个新的
Application,将rootEl指定为div创建
- 在应用实例上调用
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 的一部分),但它不会自行“运行”——require 可以。所以,无论你define,你必须在其他地方require 运行。
define 函数参数中的 return 值是您在 require 时返回的值。
在这种情况下,getStartOptions 是 私有的,但返回的 View 是之前要求 ['application'] 的值。
大致情况如下:
- 从以前开始,我们实例化这里定义的
View(我们称之为Application)
- 我们打电话给
start
-
start 将 started 设置为 true
- 当
started 为真时,视图(通过listenToOnce 调用)设置初始选项。
- 第一次设置
rootEl 时,我们将模板注入DOM。
- 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.还有很多其他选择。