以下是在处理非常大的主干应用程序后的一些提示。它不是详尽的或最终的..
分为两个目录
- 服务器目录例如
server/
- 可公开访问的目录,例如
www/
此外,当您运行构建任务时,它会将应用程序构建为可分发的版本,并放入 build/ 或 dist/ 目录。可能使用 Gulp 或 Grunt。
扩展主干
您的整个应用将包括:
您应该扩展 Backbone 类,即使它们一开始是空的。最有用的两个扩展是:
- 子视图(视图可以有一个
views 对象/函数和更多视图,当您删除父视图时这些视图会被清理)。名为 model 或 collection 的模型和集合会自动传递给子视图。
- 子路由器(很高兴在模块文件夹中包含每个模块的路由逻辑)
使用 pod 架构
就像围绕独立模块组织您的应用程序一样,例如:
www/app/modules/home/router.js www/app/modules/home/module.js www/app/modules/home/views/... 所有视图(可以有子文件夹)
www/app/modules/home/templates/
www/app/modules/home/models/
www/app/modules/home/collections
开始根据视图和子视图查看您的应用
一个页面不仅仅包含一个视图。它可能有一个特殊的“布局”视图,里面会有很多视图——一个将页面分成两半,一个分页,每个页码都有更多视图,一个视图内部有很多子视图对于每个表单元素和消息等
您可以开始将视图视为 DOM 树的阴影并进行逻辑划分 - 您认为在您的页面上可重复使用的任何内容都将其作为一个包(如果需要,它是自己的视图和模型/集合)。
模型适用于任何数据以及对数据执行的任何逻辑,如果视图显示来自服务器/api/数据库的任何内容,则通常会将其传递给视图,该视图会将所有或部分模型属性传递给模板。
如果显示信息的项目在列表中,则集合将管理每个项目的每个模型。
与模特交流
如果您发现自己想要将某些内容从一个视图传递到另一个视图,请使用共享模型。视图应该尽可能地解耦(它不需要知道它的父级)。
拥有应用状态
创建一个名为 AppState 的模型,以使用触发器和侦听器在应用程序中进行广泛通信。
有一个包文件夹(可选)
每当您在应用中遇到您认为可以重复使用的东西时,即使在未来的其他应用中,也可以创建一个包。这些通常托管在他们自己的 git 存储库中,您可以使用 package.json 或命令行将它们拉入项目中。
有一个文件夹可以用来扩展应用间的东西
为多个应用程序使用的模块提供一个扩展文件夹 - 例如。您的主干扩展可以放在这里。或者,如果您为表单创建了一个包,但想专门为这个应用做一些事情,那么在这里扩展它。
例如
www/app/extensions/view.js
www/app/extensions/model.js
www/app/extensions/collection.js
www/app/extensions/buttons/link.js // 从“按钮”包扩展链接视图。
资产
我之所以在公共 www/ 文件夹中有一个 app/ 文件夹,是因为我还可以在其中有一个资产文件夹,用于存放字体和图像等:
www/assets/css
www/assets/images
注意:也许您想尝试将资产保存在模块文件夹中(与 pod 架构内联)。我以前没有这样做过,但值得考虑。
index.html
通常,如果您使用的是 CommonJS 或 AMD,您的 index.html 将只是没有实际 DOM 元素的样板文件,您将在其中调用一个入口 js 文件。由于 CommonJS 必须编译,所以它类似于 <script src="/app.js"></script> 但对于 AMD 来说更像是:
<!--IF NOT BUILD-->
<script data-main="/app/config" src="/packages/require.js"></script>
<!--ELSE
<script src="/app.js"></script>
-->
因此,当在开发(非构建)中运行时,RequireJS 将加载 app/config.js,但在构建中,整个应用程序将在 app.js 中。有各种各样的 Grunt/Gulp 构建任务会为你做类似上面的事情(显然,条件语法只是组成)。
布局
我将创建一个扩展 extensions/view.js 的 extensions/layout.js,这将是一个简单的扩展,可以具有像正常一样的子视图(例如页眉和页脚),但也可以是一个特殊的子视图,我可以将任何视图附加到(对于身体子视图)例如像setContentView(view) 这样的方法。
我可能会创建一个名为 layouts 的模块,并在其中有一个目录modules/layout/default,其中有一个包含页眉和页脚子视图的视图。然后到达索引路由会是这样的:
app/router.js => app/modules/home/router.js => app/modules/home/module.js@index => setContentView(view from app/modules/home/views/index.js)"
路由
我会有一个应用路由器位于例如www/app/router.js 可能有一些特殊的路由,但在很大程度上只是使用指向子路由器的对象进行子路由:
subRouters: {
'store-locator': StoreLocatorRouter,
myaccount: MyAccountRouter,
sitemap: SitemapRouter
}
我会通过扩展普通的 Backbone 路由器来实现这一点(请注意,在您的扩展中,您需要在 initialize 中调用 initSubRouters) -
define([
'underscore',
'backbone'
],
function(_, Backbone) {
'use strict';
/**
* Extended Backbone Boilerplate Router
* @class extensions/router
* @extends backbone/view
*/
var Router = Backbone.Router.extend(
/** @lends extensions/router.prototype */
{
/**
* Holds reference to sub-routers
* @type {Object}
*/
subRouters: {},
/**
* Adds sub-routing
* based on https://gist.github.com/1235317
* @param {String} prefix The string to be prefixed to the route values
*/
constructor: function(options) {
if (!options) {
options = {};
}
var routes = {}, prefix = options.prefix;
if (prefix) {
// Ensure prefixes have exactly one trailing slash
prefix.replace(/\/*$/, '/');
} else {
// Prefix is optional, set to empty string if not passed
prefix = '';
}
if (prefix) {
// Every route needs to be prefixed
_.each(this.routes, function(callback, path) {
if (path) {
routes[prefix + '/' + path] = callback;
} else {
// If the path is "" just set to prefix, this is to comply
// with how Backbone expects base paths to look gallery vs gallery/
routes[prefix + '(/)'] = callback;
}
});
// Must override with prefixed routes
this.routes = routes;
}
// .navigate needs subrouter prefix
this.prefix = prefix;
// Required to have Backbone set up routes
Backbone.Router.prototype.constructor.apply(this, arguments);
},
/**
* Sets up 'beforeRoute' event.
*/
initialize: function() {
// This is a round about way of adding a beforeRoute event and must
// happen before any other routes are added.
Backbone.history.route({
test: this.beforeRoute
}, function() {});
},
/**
* Called before routes.
* @return {Boolean} false This ensures the 'route' is disabled.
*/
beforeRoute: function() {
Backbone.history.trigger('beforeRoute');
return false;
},
/**
* Adds prefix to navigation routes
* @param {String} route Non-prefixed route
* @param {Object} options Passed through to Backbone.router.navigate
*/
navigate: function(route, options) {
if (route.substr(0, 1) !== '/' && route.indexOf(this.prefix.substr(0,
this.prefix.length - 1)) !== 0) {
route = this.prefix + route;
}
Backbone.Router.prototype.navigate.call(this, route, options);
},
/**
* Initializes sub-routers defined in `this.subRouters`
*/
initSubRouters: function() {
_.each(this.subRouters, function(Router, name) {
this[name] = new Router({
prefix: name
});
}, this);
}
});
return Router;
});