【问题标题】:Is it good practice to manage view instantiation in a router?在路由器中管理视图实例化是一种好习惯吗?
【发布时间】:2014-12-17 04:11:48
【问题描述】:

所以这是我的第一个 Backbone 项目,我想知道我是否以最好的方式做事。我的应用程序基本上有两种状态,一种显示搜索框,另一种显示搜索框,其下有一个表格。我的路由器有用于搜索的路线和只有搜索视图的初始登录页面。当用户输入查询时,路由器导航到搜索路径,并且表格视图被添加到页面中。这是我的路由器:

app.Router = Backbone.Router.extend({

    routes: {
        '': 'index',
        'search/coords=:address&age=:age&rad=:rad': 'search'
    },

    search: function(address, age, rad){    
        app.statusView || (app.statusView = new app.StatusView());
        app.searchView || (app.searchView = new app.SearchView());
        app.trigger('status:loading');
        app.Practices.fetch({
            reset: false,
            success: function() {
                app.searchView.setElement($('#search-box')).render();
                var searchQuery = new app.SearchQueryModel({age: age, coords: address.split(","), radius: rad});
                if (!app.tableView){
                    app.tableView = new app.TableView({model: searchQuery});
                } else {
                    app.tableView.model = searchQuery;
                    app.tableView.refresh();
                };
            }
        });
        app.trigger('status:clear');
    },

    index: function() {
        app.statusView = new app.StatusView();
        app.searchView = new app.SearchView();
        app.footerView = new app.FooterView();
        app.searchView.setElement($('#search-box')).render();
    }
});

如您所见,我的视图在索引路径中被实例化,然后在您搜索时使用相同的视图,除非用户直接进入搜索页面,在这种情况下视图在此处被实例化。如果这不是非常理想,我会感到惊讶,因为检查视图是否已经存在于搜索路径中似乎很笨拙。有没有更好的做事方式?

【问题讨论】:

    标签: backbone.js


    【解决方案1】:

    可以说它还不错,但有一种更好的方法。

    至于现在,您的路由器负责连接 URL 与应用程序状态以及视图和模型控制。第二个可能与路由器分离,因此您将需要控制器抽象,但 Backbone 不提供“开箱即用”控制器。

    但这不是问题,你可以使用plugin或者看一下Marionette.js中的Controller实现

    这里的主要思想是正确划分应用部分之间的职责:

    1) Router - 保持路由并将 URL 与控制器操作挂钩

    2) Controller - 管理视图和模型(创建、删除、获取等)

    3) 查看 - 监听模型和 DOM 事件并渲染数据

    4) 模型 - 提供实际数据并使用数据。

    【讨论】:

    • 感谢您的回答,我认为我需要一个控制器。我的应用程序很简单,我认为我可以自己制作。我感到困惑的一件事是谁拥有这些观点。我是在控制器的范围内实例化它们并让控制器管理与它们的所有交互,还是按照我现在的方式将它们设置在应用程序命名空间下?
    • 我遵循的规则-如果怀疑谁负责某些操作-将其留给控制器;)因此,与视图和模型类的所有交互都应该执行控制器(实例、获取、删除、渲染)。应用程序命名空间应该用于全局任务 - 存储通用类和方法以及在其他应用程序部分中使用的其他一些东西。
    【解决方案2】:

    首先欢迎来到 Backbone。它是一个可爱的框架,可以让您根据自己的喜好使事物变得美丽或丑陋。您的问题是关于视图实例化应该在哪里,就良好实践而言。当然,这样做似乎有点错误,因为它通过处理 url 路由和视图实例化违反了 德米特法则

    但是视图必须从某个地方运行,对吗?如果不是路由器那在哪里?

    所以我有两个回应:

    1. 如果您的应用程序很简单,并且您只想使用骨干网,那么您可能会没事的。很多人让单页应用程序框架使原本简单的应用程序复杂化。我不是想偷懒,但你现在拥有它是 Backbone 中初学者的自然选择。如果这是你的情况,那就停在这里。

    2. 如果您想使用骨干网的全部功能来定制框架,请继续阅读。

    所以我的设置旨在能够使用一些样板函数启动一个新项目,并且只创建几个特定于新应用程序的类。路由处理和所有这类事情对我来说似乎足够低级,它应该只是我不想经常查看的某些配置的一部分。结果是我的路由器看起来像这样:

    define([
        'autorouter'
    ], function(AutoRouter){
        var AppRouter = AutoRouter.extend({
            autoRoutes: {
                ":page" : "routeDirect",
                ":page/:object" : "routeDirect",
                ":page/:object/:action" : "routeDirect",
                "": "routeDirect"
            }
        });
    
        return AppRouter;
    });
    

    然后对于每个新项目,我都有一个保存非默认路由的文件,例如:

    define(function(require){
       return {
            "schedule" : require('screens/schedule')
          , "logout" : require('screens/logout')
          , "login" : require('screens/login')
          , "create" : require('screens/create')
          , "upload" : require('screens/upload')
          , "select" : require('screens/selection')
          , "inventory" : require('screens/inventory')
          , "describe" : require('screens/description')
       }
    });
    

    我将每个屏幕放入它自己的文件中(使用 requirejs 进行多文件依赖管理)。额外的变量被传递给screen

    每个screen 都是特定用户体验的大脑,负责加载视图并可能在该屏幕处于活动状态时处理一些事件。

    如果这看起来是一个有趣的设置,那么我就是这样做的:

    对于路由器本身,我使用从Derick Bailey 借来的样板类,并稍作修改:

    define([
        'jquery', 'underscore', 'backbone'],
    function($, _, Backbone) {
        var AutoRouter = Backbone.Router.extend({
          constructor: function(options){
              Backbone.Router.prototype.constructor.call(this, options);
              var that = this;
              that.app = options.app;
              if (this.autoRoutes){
                that.processAutoRoutes(options.app, that.autoRoutes);
              }
          },
    
          processAutoRoutes: function(app, autoRoutes){
              var method, methodName;
              var route, routesLength;
              var routes = [];
              var router = this;
    
              for(route in autoRoutes){
                routes.unshift([route, autoRoutes[route]]);
              }
    
              routesLength = routes.length;
              for (var i = 0; i < routesLength; i++){
                route = routes[i][0];
                methodName = routes[i][1];
                method = app[methodName];
                router.route(route, methodName, method);
              }
          }
        });
    
        return AutoRouter;
    });
    

    我从来不用看它,但我确实需要向它传递一个应用实例。例如:

    this.appRouter = new AppRouter({app : this});
    

    最后是我的路线方向功能:

    define(function(require){
    
       var pathParser = function(path){
         return Array.prototype.slice.call(path);
       }
    
       var pathApply = function(path, routes, context){
         var pathArray = pathParser(path);
         var primary = pathArray[0];
         if (routes.hasOwnProperty(primary)){
             routes[primary].apply(context, pathArray.slice(1));
         } else {
             routes["default"].apply(context, pathArray.slice(1));
         }
       }
    
       return function(path){
         //NOTE PLEASE that this references AutoRouter
         //Which has an app property
         var oApp = this.app;
         var pathRoutes = _.extend(require('urls'), {
           "default" : require('screens/default')
         });
         pathApply(arguments, pathRoutes, oApp);
       };
    });
    

    那么,我让事情变得更好了吗?好吧,如果您只用一两个屏幕做一些非常简单的事情,那么您当然不想从头开始构建这种设置。但是,如果您像我一样,并且希望能够快速生成新项目,那么拥有像上面两个类这样的样板文件允许一个 JSON 对象告诉应用程序我应该将哪些路由发送到哪些屏幕。然后我可以将所有逻辑放在适当的位置,从而实现关注点分离。这就是为什么我认为 Backbone 如此令人愉快的原因。

    【讨论】:

      【解决方案3】:

      我对您的问题的理解是,您每次点击搜索时都会触发一条路线。

      如果您是这样做的,则使用视图事件哈希(用于捕获和处理视图中发生的事件)进行搜索。不要使用路由。在视图中定义一个事件哈希并有一个回调来处理搜索。

      var myAppEventBus = _.extend({},Backbone.Events);
      
      var myAppController = {
      
        function : search(options) {
          // create an instance of the collection and do a fetch call passing the 
          // search parameters to it.
        
          var searchResultsCollection = new SearchResultsCollection();
        
          // pass search criteria, the success and error callbacks to the fetch
          // method.
          var that = this;
          searchResultsCollection.fetch(
            {
              data:that.options,
              success : function() {
                  // Pass the fetched collection object in the trigger call so that
                  // it can be
                  // received at the event handler call back
                  var options = {
                    "searchResultsCollection" : that.searchResultsCollection;
                  };
                  myAppEventBus.trigger("search_event_triggered",options);
           
              },
              error : function() {
                // do the error handling here.
              }
            }
          );
          
           }
      };
      
      // Application Router.
      
      var MyAppRouter = Backbone.Router.extend({
        routes : {
          'search/coords=:address&age=:age&rad=:rad': 'search'
        },
        
        search : function(searchParams) {
          // Fetch the query parameters and pass it to the view.
          
          var routeSearchExists = false;
          var searchOptions = {};
          var options = {};
          
          if(searchParams) {
            routeSearchExists = true;
            // If search params exist split and set them accordingly in
            // the searchOptions object.
            
            options.searchOptions = searchOptions;
          }
          
          // Create and render the search view. Pass the searchOptions
          var searchView = new SearchView(options);
          searchView.render();
          
          // Create and render an instance of the search results view.
          var searchResultsView = new SearchResultsView();
          searchResultsView.render();
          
          // If there are search parameters from the route, then do a search.
          if(routeSearchExists) {
            searchView.search();
          }
        }
        
      
      });
      
      // The main view that contains the search component and a container(eg: div) 
      // for the search results.
      var SearchView = Backbone.View.extend({
        el : "#root_container",
        
        searchOptions : null,
        
        initialize : function(options) {
          // Intialize data required for rendering the view here.
          // When the user searches for data thru routes, it comes down in the 
          // options hash which can then be passed on to the controller.
          if(options.searchOptions) {
            this.searchOptions = options.searchOptions;    
          }
        },
        
        events : {
          "search #search_lnk":"initSearch"
        },
        
        initSearch : function(event) {
          event.preventDefault();
          var searchOptions = {};
          // Fetch the search fields from the form and build the search options.
          myAppController.search(searchOptions);
          
        },
        
        search : function() {
          if(this.searchOptions) {
             myAppController.search(searchOptions);
          }
        }
        
      });
      
      // The view to display the search results.
      var SearchResultsView = Backbone.View.extend({
        
        
        searchResultsCollection : null;
        
        initialize : function(options) {
          
          // Handling the triggered search event.
          myAppEventBus.on("search_event_triggered",this.render,this);
        },
        
        
        render : function(options) {
          //search results collection is passed as a property in options object.
          if(options.searchResultsCollection)
             //Render your view.
          else
            // Do it the default way of rendering.
            
        }
      });
      1. SearchView 是根视图,其中包含搜索组件和用于保存搜索结果的 div 等容器。
      2. SearchResultsView 显示搜索结果。
      3. 单击搜索选项时,事件回调 (initSearch) 会获取输入的搜索数据。
      4. 调用 myAppController 对象上的搜索方法并传递搜索查询。
      5. 创建搜索集合的一个实例并调用 fetch,将搜索查询以及成功和错误回调传递给它。
      6. 成功后,将触发自定义主干事件以及获取的集合。
      7. 调用此事件的回调(SearchResultsView 中的渲染方法)。
      8. 回调呈现搜索结果。
      9. 在路由器中加载时,可以创建两个视图的实例(结果视图将为空)并附加到 dom。
      10. 如果您希望通过 url 上的多个查询字符串进行搜索,那么我建议您使用以下路线。 搜索?*queryString。
      11. 在路由回调中调用实用函数,拆分查询字符串并返回搜索对象并将搜索字符串传递给视图。

      【讨论】:

      • 抱歉回复晚了。我接受了它,因为它似乎是我需要的,但我只是想实现类似的东西并意识到我认为我需要路线......我希望用户能够通过地址进行搜索,就像他们去到 site.com/search#q=test 它会搜索该参数。我不明白你的方法如何促进这一点?
      • 我已经更新了上面的答案,以便它处理来自路由的搜索字符串。这现在可以处理路线搜索和用户触发的视图搜索。更改仅在路由器回调中,并在 SearchView 中添加了一个额外的方法,该方法只是将搜索委托给控制器。如果路由中存在搜索,则在创建视图后调用此方法。
      • 太棒了,谢谢。我做过类似但略有不同的事情。我的搜索路由调用控制器方法来创建视图,然后调用控制器搜索方法,该方法在每个视图上呈现并调用适当的方法,因此所有模型-视图交互都由控制器完成。路由器只是告诉控制器该做什么。我认为我的困惑只是由于缺少控制器造成的。现在一切似乎都更好地协同工作了。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-02-27
      • 1970-01-01
      • 2017-02-20
      • 1970-01-01
      相关资源
      最近更新 更多