【问题标题】:Backbone project organization骨干项目组织
【发布时间】:2014-11-03 16:20:51
【问题描述】:

我在想出一种干净、可靠的方式来组织我的 Backbone 应用程序方面有些挣扎。我正在使用 Requirejs、Handlebars 和 Requirejs Text 插件来动态加载 HTML 视图。为简化起见,假设该网站有以下页面:

首页:显示产品集合

关于:静态页面

帐户: 包含帐户信息。购买的产品,允许进行各种更新。很多功能。有标签可以导航到不同的部分。

所以我打算使用 SPA 将新页面加载到 div('.backbone-view')中。我是否应该有一个带有 el: $('.backbone-view') 的通用 AppView,它在路由更改然后加载适当的模板时被调用?或者我应该对每个页面(homeView、aboutView、accountView)都有一个视图,所有页面的 el 都设置为骨干视图?

除此之外...除了产品之外,我还需要模型吗?对于关于页面的静态,我只需加载 html 模板就可以了。但是对于产品,我需要调用 products 集合,它呈现每个产品视图,每个视图都与一个产品模型相关联。很好……但是我在哪里初始化这些产品结构呢?当我路由到主页时,我会在那里做吗?我有这个伪代码:

  routes: {
        '': 'home',
        'about': 'about',
        'my-account': 'myAccount',
        '*default': 'home'
    },

    'home': function() {
        // Grab template for home page

        // Load up products

        // Replace $('.backbone-view') with home page template populated with products
    },

    'about': function() {
        // Grab about template and replace $('.backbone-view') with its contents
    },

    'myAccount': function() {
        MIND EXPLOSION
    }

我认为一个大问题是我不清楚 Views 的用途...它们可以仅用于页面转换,还是应该始终附加一个模型?如果是前者,我至少需要一个 AppView,然后是每个页面的 Views,对吧?我不知道我会将每个步骤委派到哪里......所以任何帮助都非常感谢。

感谢您的帮助!

【问题讨论】:

    标签: backbone.js requirejs handlebars.js organization


    【解决方案1】:

    以下是在处理非常大的主干应用程序后的一些提示。它不是详尽的或最终的..

    分为两个目录

    1. 服务器目录例如server/
    2. 可公开访问的目录,例如www/

    此外,当您运行构建任务时,它会将应用程序构建为可分发的版本,并放入 build/dist/ 目录。可能使用 Gulp 或 Grunt。

    扩展主干

    您的整个应用将包括:

    • 视图和子视图
    • 路由器和子路由器
    • 型号
    • 收藏

    您应该扩展 Backbone 类,即使它们一开始是空的。最有用的两个扩展是:

    • 子视图(视图可以有一个views 对象/函数和更多视图,当您删除父视图时这些视图会被清理)。名为 modelcollection 的模型和集合会自动传递给子视图。
    • 子路由器(很高兴在模块文件夹中包含每个模块的路由逻辑)

    使用 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.jsextensions/layout.js,这将是一个简单的扩展,可以具有像正常一样的子视图(例如页眉和页脚),但也可以是一个特殊的子视图,我可以将任何视图附加到(对于身体子视图)例如像setContentView(view) 这样的方法。

    我可能会创建一个名为 layouts 的模块,并在其中有一个目录modules/layout/default,其中有一个包含页眉和页脚子视图的视图。然后到达索引路由会是这样的:

    app/router.js =&gt; app/modules/home/router.js =&gt; app/modules/home/module.js@index =&gt; 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;
    });
    

    【讨论】:

    • 很好的答案,谢谢多米尼克!我已经开始使用这个模板重建我的项目,它已经变得更有条理了。我只是有几个后续问题:如果我有一个将显示在每个页面上的标题的部分模板(但也是基于其他因素的动态),我应该把它放在扩展文件夹中,因为它是共享的每个模块?另外,当我点击具有视图和子视图的路线时,您能解释一下应用程序流程吗?比如,路由器——>调用主视图的方法——>主视图中调用子视图的方法......?谢谢!
    • @user918065 Np 我添加了更多信息。我只是模糊地谈到了一些事情,所以如果你遇到困难,请随时问 Qs。而且您可能真的不需要太担心使用自己的 repos 和东西制作包。
    • 谢谢!快速提问:当我通过链接导航到它们时,我的子路由器正在工作,但当我直接导航到 url 时却没有。在那种情况下永远不会调用子路由器......
    • @user918065 在你的app/router.js 你应该有 a) routes 非子路由路径的对象 b) subRouters 在模块路由器中传递的对象和 c) 在 initialize 中调用 this.initSubRouters(); -- 您可以将initSubRouters 放在扩展名中,这样您就不必手动执行此操作。当然,您还必须在应用程序初始化时执行Backbone.History.start,并通过路由器传递锚链接
    • 啊……我是个傻瓜。在调用 initSubRouters() 之前,我调用了 Backbone.History.start。非常感谢!你非常乐于助人。
    猜你喜欢
    • 2012-01-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-15
    • 2012-10-21
    • 2011-10-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多