计划,每天学习两小时,坚持带来大改变。

Marionettejs 全面介绍(3) 视图View

前端开发 阿尤 7751浏览 1评论
粗略地与翻译自 Smashing Magazine:

为了帮助你接触到完整的Marionette的潜力,我们已经准备了一整本的电子书、里面有大量的手把手的例子,可以通过 Smashing Library 来进行购买。—— Ed

在系列文中,我们已经讨论了Application与Module。 这次,我们会看一眼Marionette是如何帮助Backbone的视图变得更好的。Marionette继承了Backbone的基础View类来给我们提供更多的内建的功能,来避免大部分的通用性的代码量、同时也使更多通用性的代码能够通过配置的方式来达成。

我强烈建议你先回去重新读关于Application与Module的文章,如果你还没有读前面的内容。在这篇文章中有些提到的内容会是关于前面的文章已经描述过的,然后这是一个关于Marionette的系列文,所以如果你希望学习Marionette,你应该通读整个系列文。

事件绑定

就在最近,Backbone的View常常被错误地使用,导致了被称为“僵尸视图”的问题。这个问题的原因是许多视图会监听了model的方法,这本身是完全无害的。问题来自于当视图已经不再需要并且已经“被遗弃”时,他们并不会停止监听相应的model的事件,意味着model依然有着视图的引用,使得视图的内存不会被垃圾回收器所回收。这导致了内存会随着时间不停地增加,而且这些视图始终会响应那些model的事件,虽然它已经不再会进行任何渲染行为,因为它已经被从DOM节点中移除。

许多Backbone的扩展或插件——包括Marionette,都预先处理了这个问题。但我不会说明更多的相关细节,因为Backbone的开发者们已经在最近的1.0版本自己解决了这个问题(终于!),通过对Events使用listenTo和stopListening方法,而Events正是Backbone.View的基类。Marionette的开发者也已经把这部分功能实现从代码中移除,但这并不意味着Marionette不能帮助我们处理更多的关于事件绑定的问题。

Marionette.views提供了几个我们能够扩展的属性,使view上的model与collection的事件绑定变得更加简单:modelEvents与collectionEvents。只需要简单地传进一个对象,key为我们监听的model或collection事件的名称,而值为事件触发时需要调用的函数名称。下面是一个简单的示例:

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
     modelEvents: {
          'change:attribute': 'attributeChanged render',
          'destroy': 'modelDestroyed'
     },

     render: function(){ … },
     attributeChanged: function(){ … },
     modelDestroyed: function(){ … }
});

这完成了与listenTo一样的操作,而只需要更少的代码。他与下面使用listenTo来实现的代码是一致的:

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
    initialize: function() {
        this.listenTo(this.model, 'change:attribute', this.attributeChanged);
        this.listenTo(this.model, 'change:attribute', this.render);
        this.listenTo(this.model, 'destroy', this.modelDestroyed);
    },

    render: function(){ … },
    attributeChanged: function(){ … },
    modelDestroyed: function(){ … }
});

这里有几个注意事项。首先,modelEvents是用于监听view的model,collectionEvents用于监听view的collection(分别为this.model与this.collection)。其次,你可能注意到了这里有两个change:attribute事件的回调函数。如果你指定了一个字符串作为回调,你可以绑定任意多的回调函数,只需要用空格隔开即可。所有的回调函数都会被唤起(invoke),只要相应的事件被触发。而这些函数的字符串名称都应该是这个view的一个实例方法。

也可以选择用另一种方式进行modelEvents与collectionEvents的指定。首先,不传入一个字符串名称,而使用匿名函数替换:

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
    modelEvents: {
        'change': function() {
            …
        }
    }
});

这也许不是最佳实践,但需要的时候也可以这样做。同时,也可以不使用对象作为忏悔赋值给modelEvents或collectionEvents,你也可以直接传入一个函数。这个函数需要返回一个对象,而这个对象描述了所有的事件与回调。这允许你动态地创建一系列的事件与回调。我想像不出在什么情况下你会需要动态地确定事件的绑定,但如果你有这样的需求,这功能就会变得相当好用。

Backbone.Marionette.View.extend({ // We don't normally directly extend this view
    modelEvents: function() {
        return {'destroy': 'modelDestroyed'};
    },

    modelDestroyed: function(){ … }});

modelEvents与collectionEvents功能非常符合Backbone与Marionette中十分常见的模式(Pattern):将常用代码降级为配置。Backbone自己有着events对象做着这种事:使得你能轻松地监听DOM的事件。Marionette的modelEvents与collectionEvents的灵感就直接来自于Backbone原有的events配置。你将会不断地看到这种配置,尤其是在之后的系列文章里,当我们提到ItemView,CollectionView与CompositeView时。

解构一个view

像我在前面章节提过的,有时候一个view需要被废弃或移除,因为一个model被销毁或者因为我们需要在这个地方显示一个不同的视图。使用 stopListening,我们能够清理全部的事件绑定。但剩下的那些东西怎么清理呢?Backbone有一个remove函数会帮我们调用stopListening并且同时移除DOM的所有视图。

通常情况下,你需要的就只是这些,但Marionette做得更多一点,通过添加一个close函数。当使用Marionette的view时,你将会使用close函数而非remove函数,因为它也会清理所有的Marionette的view默默设置的配置。

另一个Marionette的close方法带来的好处是,它也会触发一些事件。在开始关闭一个view时,它会触发before:close事件,然后在完成时则会触发close事件。而在事件之外,你还可以在view上指定一些方法来在这些事件触发之前执行。

Backbone.Marionette.View.extend({ // We don't normally directly extend this view onBeforeClose: function() { // This will run just before the before:close event is fired }, onClose: function(){ // This will run just before the close event is fired }}); 

如果你想在视图彻底消失之前运行一些代码,你可以使用onBeforeClose与onClose视图方法,使得你可以不添加任何事件监听就自动执行这些方法。简单地声明这些方法,然后Marionette就会确保他们被唤起。当然,其它对象就只能通过监听这个视图的事件这个方法了。

DOM刷新

回到我们讨论过的Application,我提到过很多次Region的概念。我不会在这里说太多这方面的内容(当关于views的文章说完后,我会讨论region的细节),但我们知道Region是一个对象、他在一个DOM里管理了view的展现、隐藏、抛弃等。让我们看看在下面的代码是如何在一个Region里渲染一个view的:

var view = new FooView(); // 假设FooView已经定义 region.show(view); //假设region已经实例化,可以使用`show`来渲染这个view 

当你使用show方法时,它会渲染这个view(所有Marionette实现的的View类都是基于基础的View类,在通过render方法触发render事件的同时也会调用onRender方法),将这个view附加到这个DOM节点上,同时也意味着一个show事件被触发,所以组件能知道这个view已经通过一个Region进行了渲染。在一个view被渲染并显示时,如果这个view重新渲染了,它就会触发一次DOM刷新。

当前来说因为有个BUG所以这并不是真的,但开发者们正在处理这个问题。现在,当一个view渲染时,它会有一个状态说明了它已经渲染;然后,当这个视图被显示时,它会有一个状态说明它已经显示。当这两个状态都已经激活,它就会触发一个DOM刷新,而在这之后,DOM刷新将在渲染或显示时都进行刷新。记得这个特性,如果有一天你需要用到它。

当一个DOM刷新被触发时,它会执行view的onDomRefresh方法(如果你有定义),然后在view上触发一个dom:refresh事件。这在UI插件开发上经常使用(比如jQuery UI, Kendo UI等)。通常,当一个视图渲染时,它在渲染结束前不会被插入到DOM节点中。这意味着你在插件的render或onRender方法中并不能使用这个插件。

然而,你可以在onShow方法中使用这个插件(它会在show事件之前前触发),因为一个Region应该已经被附加在了DOM节点中(我们会在将来讨论这个)。现在,因为这个view已经显示,所以你会知道这个view已经在DOM节点中;所以,每次调用render时,一次DOM刷新都会在渲染完成后执行,然后你可以重新安全地调用这个UI插件的功能。

DOM触发器

有时候,当一个用户点击了一个按钮,你会想要响应这个事件,但你并不知道这个视图是如何工作的。取而代之的,你希望这个视图触发一个事件,所以其它的模块可以监听这个事件并响应它。假设你有像下面这样的代码:

Backbone.Marionette.View.extend({ //我们通常不会直接继承这个View类 events: { 'click .awesomeButton': 'buttonClicked' }, buttonClicked: function() { this.trigger('awesomeButton:clicked', this); } }); 

这个用于管理点击事件的函数只是触发了一个视图的事件。Marionette有一个特性允许你指定一个对象来简化这些代码。在扩展一个View时通过指定trigger参数,你可以赋值一个非常像events参数的对象;但是,这个对象并不是提供view的方法来唤起,而是提供一个需要触发的事件的名称。所以,我们可以将前面的代码转换成下面这个样子:

Backbone.Marionette.View.extend({ //我们通常不会直接继承这个View类 triggers: { 'click .awesomeButton': ' awesomeButton:clicked ' }}); 

然后它将会做几乎一模一样的事。而两段代码有一个主要的区别:传给触发的事件的参数列表。在第一个片段中,我们传给这个事件的参数只有this,也就是这个view;而使用triggers,Marionette会传一个对象,它带着三个属性传给每一个函数。这三个属性如下:

  • view:触发事件的view本身的引用
  • model:view的model参数的引用,如果它存在
  • collection:view的collection参数的引用,如果它存在

所以,如果你通过前面的片段进行事件的订阅,它会像下面这样写:

// view即是前面定义的View类型的一个实例 view.on('awesomeButton:clicked', function(arg) { arg.view; // view实例 arg.model; // view的model arg.collection; // view的collection } 

我知道这不是一种过剩的使用场景,但在一些应用场景下,它会省掉很多麻烦。

DOM对象缓存

通常来说,this.$el并不是唯一的需要操作的DOM元素。在那些情况下,大多数人会做些这样的事情:

Backbone.Marionette.View.extend({ // 我们通常不会直接继承这个View render: function() { this.list = this.$('ul'); this.listItems = this.$('li'); . . . // 现在我们在这里或其它方法里面使用它们 }}); 

再一次,Marionette使这些代码简化为一个简单的配置。只需要简单地指定一个ui属性,这个对象指定了需要对应的名称与选择器:

Backbone.Marionette.View.extend({ // 我们通常不会直接继承这个View ui: { list: 'ul', listItems: 'li' }}); 

你就可以直接通过this.ui.x访问这个DOM元素,x就是你在那个对象里指定的名称,比如this.ui.list。这个ui参数会被通过bindUIElements方法转换为缓存的jQuery对象。如果你自己继承了Marionette.View,而不是一些现有的Marionette已经提供的View类型,你会需要自己去调用这个方法;其它情况下,这个方法都会被自动地调用。

Backbone.Marionette.View.extend({ //我们通常不会直接继承这个View ui: { list: 'ul', listItems: 'li' }, render: function() { // 渲染模板或生成你自己的HTML,然后…… this.bindUIElements(); // 现在你可以操作这些DOM元素 this.ui.list.hide(); this.ui.listItems.addClass('someCoolClass'); }}); 

结论

我们已经看到了Marionette带给视图非常丰富的功能,它们能降低通用性代码的数量及复杂度,但我们还没有接触到一些最重要的部分。Marionette.View并没有为我们处理任何的渲染职责,但Marionette有另外三个视图类型来做这些工作: ItemView, CollectionView 与 CompositeView。

这些视图类型,就是你在代码中实际会继承到的类(注意到本文上面代码片段里的“我们通常不会直接继承这个View”的注释),会提供一些较小的配置细节,然后帮你处理剩余的渲染操作。我们会在下一篇文章里完整介绍这些是如何做到的。而现在,先仔细考虑这些已经介绍了的功能。

译后记

对于原文来说,还有着非常多的章节,包括了如下内容:

  • 第4章:ItemView 条目视图
  • 第5章:CollectionView 集合视图
  • 第6章:CompositeView 复合视图
  • 第7章:Region and Layout 区域与布局
  • 第8章:Events, Commands, and Requests and Responses 事件、命令、请求与响应
  • 第9章:AppRouter 应用路由器
  • 第10章:Controller 控制器
  • 第11章:Introducing Our Example Application 介绍我们的示例应用
  • 第12章:Decisions and Setups 决策与配置
  • 第13章:Building a Foundation 建立一个基础
  • 第14章:Managing Data 管理数据
  • 第15章:The Home Screen 首页
  • 第16章:The Quiz Creator 测试创造器
  • 第17章:Taking and Reviewing Quizzes 使用与回顾测试
  • Additional Resources 附加资源

但这些都是需要在Smashing Shop里面购买的内容了,我的翻译工作也就告一段落。


转自 :http://blog.waterwu.me/marionettejs-thorough-introduce-3-view/

转载请注明:阿尤博客 » Marionettejs 全面介绍(3) 视图View

游客
发表我的评论 换个身份
取消评论

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  • 验证码 (必填)点击刷新验证码

网友最新评论 (1)

  1. 访客
    挺好!
    the5fire 10年前 (2015-04-01)回复