现在的位置: 首页 > 综合 > 正文

深入理解_WidgetBase

2013年09月07日 ⁄ 综合 ⁄ 共 4521字 ⁄ 字号 评论关闭

通过本文您将学到Dijit包中的_WdgietBase模块是怎么回事,以及它作为Dojo Toolkit中所有widget的基础是如何工作的。

难度:中等
Dojo版本:1.7
原作者:Tom Trenka
译者:Oliver

简介

Dijit包的基础架构,以及创建自定义widget的能力,在很大程度上依赖于一个定义在dijit/_WidgetBase模块的基类。虽然还有另一些常用的模块在开发基于Dojo的web应用时很有价值(例如Dojo parser和Dijit模板系统),但这个_WidgetBase是创建任何自定义widget时都要用到的。在本文中,您将学习到这个widget基础设施是如何工作的。

注意:如果您具有早期Dojo版本的使用经验,可能对dijit/_Widget模块比较熟悉。虽然目前dijit/_Widet仍然存在,并且继承了dijit/_WidgetBase,但在撰写您自己的widget时最好还是直接继承djit/_WidgetBase。dijit/_Widget将很有可能从Dojo2.0中移除。

要理解Dijit系统,最重要的一点就是理解一个widget的生命周期。这个生命周期主要是关于widget的初始化过程。也就是说,从一个widget的创建,到完全可被您的应用所使用,再到销毁该widget及其所涉及的DOM结点。

注意:Widget和WidgetBase之前之所以加上了下划线"_",是因为它们不应该被直接实例化,而应通过dojo的declare机制来继承。

_WidgetBase中的方法可分为两条主线:一组在创建过程中顺序调用的方法,以及一套在使用widget的过程中以最小的数据绑定来获取/设置属性的方法。现在让我们来研究一下widget的创建过程。

Dijit生命周期

任何一个以_WidgetBase为基类的widget在其初始化的过程中都会依次调用几个特定的函数。下面以调用顺序列出这些函数:
  • constructor (所有原型通用,初始化时被调用)
  • postscript (由declare声明的所有原型通用)
  •      create
  •           postMixInProperties
  •           buildRendering
  •           postCreate
  • startup
这些方法用于处理以下事情:
  • 何时定义初始值
  • 如何创建初始值的可视化表示
  • 这些可视化表示何时连入DOM树
  • 何时执行依赖于连入DOM树的DOM结点的代码

postCreate()

在创建自定义widget时,目前最需要记住的一个方法就是postCreate。当widget的所有属性都已定义,且代表该widget的DOM结构已被创建(但在连入主DOM树之前)时,这个方法被调用。这个方法之所以重要,是因为这是开发人员,也就是您,有机会在widget最终呈现给用户之前做最后的调整的主要场所。这些调整包括设置任何自定义的属性,等等。在开发一个自定义widget时,大多数(如果不是所有的话)的定制都会出现在这个函数中。

startup()

可能Dijit生命周期中的第二重要函数就是startup了。这个方法在widget的DOM结构被连入主DOM树之后被调用。在该函数之前,这个widget的所有子widget都已被创建并启动了。这个函数对于复合widget(例如布局widget)是非常有用的。

注意:当使用编程方式初始化一个widget时,务必调用startup方法,且必须在将该widget放置到文档DOM树中以后再调用。创建好widget之后忘记调用startup方法是一个很常见的错误,让您因为搞不懂为何widget不正常显示而抓狂。

销毁方法

除了初始化方法之外,dijit/_WidgetBase还定义了一些销毁方法(以调用顺序列出):
  • destroyRecursive
  •      destroyDescendants
  •      destroy
  •           uninitialize
  •           destroyRendering
在创建您自己的widget时,任何必要的自定义销毁行为都应该定义在destroy方法中(别忘了调用this.inherited(arguments))。
Dijit自带的destroy方法已经能够自动处理DOM结点和大多数对象,因此您大可不必从头开始编写这些逻辑。

注意:虽然destroy是销毁widget的核心方法,但在显式销毁一个widget时最好调用destroyRecursive。这个方法能保证该widget的所有子widget也被销毁。

DOM结点引用

一个widget通常是某种用户界面,因此大都具有某种DOM结构。_WidgetBase定义了一个表中属性,叫做domNode,它是囊括整个widget的DOM结点的引用。如果您需要对widget的DOM树做某些操作(例如将整个widget移动到DOM树的另一个位置),您随时可以获取这个结点的引用。而且这个引用在postCreate被调用的时候就已经存在了。

除了domNode属性之外,某些widget还定义了containerNode属性。这是对widget的一个内部结点的引用,这个结点里包含了一些HTML内容甚至是其他widget,它们往往是在您的widget声明之外定义的,例如一个通过声明方式创建的子widget。

注意:我们将在另一篇教程中讨论containerNode属性的重要性。目前只要知道这个属性存在于某些widget中即可(尤其要注意它存在于所有继承自dijit/_Container的widget)。

Getter和Setter

除了关于初始化和销毁的方法,_WidgetBase还预定义了许多所有widget都需要的属性,以及一整套为属性定义getter和setter的机制,这套机制通过标准的get和set方法起作用,这两个方法在所有widget中都有定义。您可以在代码中添加含有如下签名模式的私有方法来利用这套机制:
// 您的widget的“foo”属性:
 
//  自定义getter
_getFooAttr: function(){ /* do something and return a value */ },
 
//  自定义setter
_setFooAttr: function(value){ /* do something to set a value */ }

一旦用这种方法声明了这些方法对,您就可以通过_WidgetBase的标准的get和set方法来访问您的自定义属性了。例如在上面的例子中,您可以这么写:

//  假设widget实例名为myWidget:
 
//  获取"foo"属性的值:
var value = myWidget.get("foo");
 
//  设置"foo"属性的值:
myWidget.set("foo", someValue);
这个标准允许其他widget或控制逻辑能够通过统一的方式与一个widget交互,并且在一个属性被访问时提供给您执行一些自定义逻辑的能力(例如对一个DOM片段的修改,等等),或者调用其他的方法(例如触发一个事件或通知)。假设您的widget有一个自定义属性value,您希望在value值被改变时通知其他人(可能通过您定义的一个onChange方法):

//  假设我们的属性称为"value":
 
_setValueAttr: function(value){
    this.onChange(this.value, value);
    this._set("value", value);
},
 
//  一个用于dojo/on的函数
onChange: function(oldValue, newValue){ }

如您所见,这套机制使得定制getter和setter的行为变得非常方便。


注意:在撰写您自己的widget时,对于任何需要特殊逻辑来实现自定义属性的获取和修改的情况,您都应该创建对应的getter和setter方法。而在使用您自己的widget时,也必须总是通过get()和set()方法来访问属性。只有这样才能正确地调用自定义的getter和setter逻辑。另外,在创建自定义的setter方法时,您应当总是使用内部的_set方法来更新内部值,从而使dojo/Stateful的watch方法能正常工作。这个dojo/Stateful也是所有widget都继承的基类。

监听事件和主题

最后,_WidgetBase为Dojo的两个最重要的事件机制on()和subscribe()提供了封装。这些方法主要是为了方便,其次是为了便于准确地初始化和销毁widget。您不需要自己来定义这些方法,它们都是直接可用的。

使用.on()和.subscribe()都是很容易的:
//  assume we have a button widget called "btn",
//  and we want the button to listen to the "bar" event on foo:
 
btn.on(foo, "bar", function(){
    //  注意"bar"在btn的上下文内执行!
    this.set("something", somethingFromFoo);
});
主题的订阅(通过Dojo的pub/sub系统)也可用类似的方法实现。

在widget的基础设施中定义on和subscribe的好处是,widget能够跟踪它的所有事件和主题订阅,保证它们在widget被销毁时被正确地取消绑定(或取消订阅),从而避免内存泄露。

预定义的属性和方法

最后,_WidgetBase提供了一组预定义的属性,以及对应的getter和setter(如果需要的话):
  • id: 用于标识该widget的唯一字符串
  • lang: 一个很少用到的字符串属性,用于覆盖默认的Dojo locale
  • dir: 有Bidi需求时很有用
  • class: widget的domNode的HTML class属性
  • style: widget的domNode的HTML style属性
  • title: 通常是指原生的工具提示HTML title属性
  • baseClass: widget的根CSS class
  • srcNodeRef: 在widget被“控件化”之前存在的DOM结点(如果有的话)。注意对于某些widget(例如模板widget)这个属性将在postCreate后被重置,因为原有的结点已被丢弃了。

结论

如您所见,Dijit的_WidgetBase基类为创建和使用widget提供了一个坚实的基础,涵盖了widget的所有方面(生命周期,DOM结点引用,getter和setter,预定义属性和事件)。我们已经看到一个widget的postCreate()方法是开发自定义widget时最重要的生命周期方法,以及在编程实例化widget时调用startup()有多么关键。我们还介绍了Dijit的getter/setter机制,以及一个widget的domNode属性的重要性。

抱歉!评论已关闭.