【AICon】探索八个行业创新案例,教你在教育、金融、医疗、法律等领域实践大模型技术! >>> 了解详情
写点什么

使用 Dijit 实现界面组件化开发

  • 2011-01-04
  • 本文字数:4721 字

    阅读完需:约 15 分钟

对于组件化的软件工程设计,很多开发人员都比较熟悉。组件化的设计适合于复杂的软件系统和团队协作开发。把软件系统划分成若干个组件,组件之间通过预先定义好的接口和协议进行通讯和协作,共同完成整个软件系统的职责。团队中的开发人员可以各自负责不同的组件。组件化的思想在桌面应用和Web 应用后台开发中比较流行,相关的技术和实践都比较成熟。而在Web 应用的前端部分,组件化一直进展得比较缓慢。这其中的原因有很多,最主要的是Web 应用的前端在开始的时候比较简单,没有组件化和设计的必要。随着Ajax 应用的流行,Web 前端部分越发复杂,用户对Web 应用的要求不断向桌面应用靠拢。HTML 语言的基本界面元素不能单独地满足这样的需求。菜单、树形控件、对话框和进度条等组件,在现在的Ajax 应用中十分常见,但是并不是HTML 默认提供的。HTML 5 规范中引入了一些新的元素,但还是不够。组件化对于Web 应用本身的代码共享和团队分工也是很有意义的。

Web 应用前端组件化的发展也是渐进的。开始的时候,只是一些简单的 HTML、CSS 加上 JavaScript 的代码示例。比如当需要实现一个多级菜单的时候,就下载相关的代码示例,就根据自己的需要进行修改。这样的组件比较难以复用。后来 JavaScript 框架开始流行的时候,有些框架本身就提供了组件的支持,包括 Ext JS、jQuery UI 和 Dojo 等。不过不同框架提供的组件模型不尽相同。

Dijit 组件模型概述

Web 应用的前端组件的定义比较宽泛。一个组件占据 Web 页面上的某个区域,并负责完成某项具体的任务。Web 组件有时候也被称为小部件(widget)。在 Dijit 组件模型中,一个 Dijit 组件是一个 JavaScript 类,可以在页面上通过 new 操作符来创建组件的实例。每个组件实例都需要与页面上的某个 DOM 元素绑定在一起。这个 DOM 元素就是该组件的根节点。在 Dijit 组件的逻辑中,就可以对该根节点进行操纵来构建用户界面。组件 JavaScript 类暴露出来的属性和方法就是该组件的接口。

Dijit 组件的使用

Dijit 组件的使用方式非常简单。首先需要在页面上加载组件的 JavaScript 代码,这通过 dojo.require 函数就可以完成。接着在页面上找到或创建一个 DOM 元素作为该组件的根节点。最后通过 new 操作符创建即可。如 new dijit.form.ComboBox({}, node) 就可以用 node 作为根元素创建一个 dijit.form.ComboBox 组件,即一个下拉列表选择框。可以看到创建 Dijit 组件的时候,使用了两个参数:第二个参数是组件的根元素,如果创建的时候不指定该根元素,会自动创建一个新的 DIV 元素作为根元素。不过该新创建的根元素一般没有加入到当前的文档树中,可以通过组件的 placeAt 方法来设置该组件在页面文档树中的位置。第一个元素则是一个 JavaScript 对象,包含了组件的配置属性。通常来说,一个 Dijit 组件是可以复用的。因此一般都会提供一些属性供使用者进行配置。通过这个参数,就可以修改这些配置。

上面提到的是程序式的方式创建 Dijit 组件,还有另外的一种方式来进行创建,即通过在 HTML 代码中以声明式的方式创建,如

复制代码
<div dojoType="dijit.Dialog" id="myDialog" title=" 示例对话框 ">
    <h3> 对话框标题 </h3>
    <div> 对话框内容 </div>
</div>

声明式的方式在一定程度上简化了开发人员使用 Dijit 组件的方式。声明式的方式与编写 HTML 代码的形式类似,只需要在一般的 HTML 元素上添加一些额外的属性就可以把 HTML 片段转换成 Dijit 组件。这对于只熟悉 HTML 语言的人来说非常方便,相当于在 HTML 语言的基本元素之上,增加了更多的可用组件。

Dijit 深入分析

Dijit 组件基本类

所有的 Dijit 组件都继承自 dijit._Widget 类。dijit._Widget 类中定义了与组件相关的一系列方法。这些方法中有一些是与组件生命周期相关的,有一些则是所有组件都需要的通用方法。了解 Dijit 组件的生命周期有利于理解 Dijit 组件的运行方式,从而更好的使用已有的组件或开发出自己的组件。

创建 Dijit 组件的过程开始于 dijit._Widget 类中的 create 方法。create 方法采用了典型的模板方法设计模式,即在该方法中封装了创建组件的基本流程。该方法会执行一些重要的操作,并依次调用其它的方法来完成整个创建过程。具体的流程包括:

  • 把创建时的配置参数混入(mix-in)到组件中。比如在创建组件的时候使用的方式是 var myWidget = new TestWidget({prop : “Hello”}, node);,那么在创建完成之后就可以通过 myWidget.prop 来获取到"Hello",在组件中也可以通过 this.prop 来访问。
  • 调用生命周期方法:postMixInProperties。该方法在配置参数混入之后调用,可以对混入的参数进行修改。
  • 把该新创建出来的组件添加到全局的组件对象注册表中。Dijit 组件都会被分配一个惟一的标识符。添加到注册表中之后,就可以用 dijit.byId 来根据标识符获取组件对象。
  • 调用生命周期方法:buildRendering。该方法用来完成构建组件的用户界面。该方法负责设置 this.domNode 的值,表示的是创建完成的组件的根元素。
  • 调用生命周期方法:postCreate。该方法在用户界面构建完成之后被调用。一般是组件内部行为逻辑的起点,类似 HTML 页面中的 onload 方法。

对于 Dijit 组件开发人员来说,创建一个新的 Dijit 非常简单。只需要用 dojo.declare 声明一个 JavaScript 类并继承自 dijit._Widget,在该类中覆写感兴趣的 JavaScript 方法即可。最简单的情况是覆写 postCreate 方法并添加组件的逻辑。

对于用来包含其它子组件的容器类组件来说,一般会覆写 startup 方法来让其调用者显式的启动这个组件。这是因为在 postCreate 被调用的时候,只是保证了组件的 DOM 节点已经被创建成功了,但是这些 DOM 节点可能并没有被添加到当前文档树中,因此不能在 postCreate 中包含与 DOM 节点大小和位置相关的代码。如果要添加这样的代码,应该在 startup 中添加。很多容器类组件都使用该方法来对其子节点进行布局。

使用 HTML 模板

如果只是使用 dijit._Widget 的话,编写 Dijit 组件会比较繁琐。比如在构建用户界面的时候,可能会需要很多的 DOM 操作,编写起来也不方便。 Dijit 提供了 dijit._Templated 用来使用 HTML 片段来定义组件的内容。HTML 片段是作为组件的内容模板。如:

复制代码
dojo.declare("TempWidget", [dijit._Widget, dijit._Templated], {
    templateString : "<div><span>Hello</span></div>"
});

TempWidget 继承了两个 JavaScript 类,除了必需的 dijit._Widget 之外,还有 dijit._Templated 的。需要保证 dijit._Widget 是父类数组的第一个元素,只有它是真正意义上的父类,其余的是混入类。dijit._Templated 类已经覆写了 buildRendering 方法来从 HTML 模板中创建组件内容的 DOM 元素,并作为组件的 this.domNode 的值。在 HTML 模板中,除了可以使用基本的 HTML 元素和属性之外,还有一些附加的实用功能:

  • 在 HTML 模板中直接引用组件中的属性。比如组件中有个属性叫 title,在 HTML 模板中想引用该属性的值,可以直接写${title}。如果属性 title 的值是"Hello",那么上述模板在运行时刻会变成Hello
  • 通过 dojoAttachPoint 来声明在组件对象中可见的 DOM 节点。当需要在组件中引用某个内部的 DOM 节点时,不需要再次进行查询,通过 dojoAttachPoint 即可。如声明
    ,就可以在组件对象中通过 this.myNode 来引用该 SPAN 元素。
  • 通过 dojoAttachEvent 来进行事件绑定。这种方式比先手工查询 DOM 节点,再通过 dojo.connect 来绑定要简单得多。如声明
    ,就意味着将组件的 test 方法绑定到按钮的 onclick 事件上。

dijit._Templated 的模板机制的这些实用功能减少了构建用户界面时的一些繁琐代码。

作为容器

如果组件是作为其它组件的容器来使用的话,就可以混入 dijit._Container 类。该类提供了对子组件的基本管理功能,包括查询、添加和删除等。使用该类的时候,需要在组件中声明一个 containerNode 的属性作为子组件的父节点。创建出 Dijit 组件之后,就可以通过 addChild 方法来添加子组件了。

销毁过程

组件在创建并运行之后,就可能需要被销毁。销毁一个 Dijit 组件很简单,只需要调用 destroyRecursive 方法即可。该方法会负责销毁当前 Dijit 组件及其包含的子组件。当一个组件被销毁的时候,其 uninitialize 方法会被调用,类似于析构函数。因此可以把组件特有的销毁逻辑添加在 uninitialize 方法中。

Dijit 组件的接口与交互

前面提到,组件之间通过设计好的接口和协议进行通讯。对于 Dijit 组件来说,它所提供的接口一般有下面这几类:

  • 公开的属性和方法。这些属性和方法类似于 Java 类中的公开的域和方法,在获取到组件对象之后可以直接使用。
  • 通过 dojo.connect 进行连接。有些组件提供了一些占位符方法用来允许其使用者监听其内部状态的变化,类似于 DOM 事件的处理。

当组件之间进行通讯和协作的时候,一般有下面几种交互的模式:

  • 传递组件对象的引用。这种做法一般是在创建新组件的时候,将其需要引用的组件对象传递进去,如 var anotherWidget = new MyWidget({parent : oneWidget}, node);。
  • 不传递对象引用,而是进行查找。这种情况适用于所依赖的组件的 ID 已知的情况。可以通过 dijit.byId 来直接进行查找。
  • 使用 Dojo 提供的全局通讯机制:dojo.publish 和 dojo.subscribe。一个组件通过 dojo.publish 来发布消息,另外一个组件则通过 dojo.subscribe 来监听相关的消息并做出处理。

一般来说,比较推荐的做法是第一种,即通过传递组件对象的引用来完成。不过当组件之间的关系比较复杂的时候,有可能需要将一个对象的引用进行多次传递。这个时候也可以考虑后两种做法。

Dijit 开发最佳实践

编写 Dijit 组件并不是一件复杂的事情,只需要按照一般的流程依次完成即可。不过 Dijit 组件本身的设计和实现比较复杂,包含了比较多的内容。下面对一些重点的地方进行讨论。

编程式和声明式的创建方式选择

这两种方式的区别只是在于开发人员的使用方式上。用声明式方式声明的 Dijit 组件,在运行时刻也是通过程序式的方式来进行创建的,由 dojo.parser.parse 方法来完成。因此,声明式的方式更像一种语法糖衣。不过声明式方式的一个好处是可以实现优雅地退化(graceful degradation),即当 JavaScript 不被支持的时候,仍然可以在页面上显示出部分内容。对于简单的和容器类的组件来说,声明式创建的方式比较好。简单的组件用声明式的方式比较简洁。而容器类的组件在创建的时候一般都需要指定所包含的内容。使用声明式的时候,组件的子节点会自动作为容器类组件的子组件来添加。而如果以程序式的方式来完成的话,需要手工创建子组件,并通过 addChild 方法来逐个添加。代码会比较繁琐。

组件状态变迁与外观样式

在开发 Dijit 组件中,经常会遇到的一个场景是根据组件内部的状态变化改变其外观样式。比如对于一个单选按钮组件来说,选中和未被选中的外观样式是不同的。典型的做法是通过切换不同的 CSS 样式名称来转换外观。比如未被选中适合的 CSS 样式名称可能是 MyRadioButton,被选中之后则变成 MyRadioButtonChecked。Dijiti 提供了 dijit._CssStateMixin 混入类来抽象这种行为。开发人员的组件只需要继承此类,并通过属性 this.baseClass 设置基础的 CSS 样式名称,就具备根据状态的变化动态修改 CSS 样式名称的能力。比如设置了 baseClass 为 myWidget,当鼠标移动到该组件上的时候,其根元素的 CSS 样式名称会自动变成 myWidgetHover。该类所支持的状态变化包括鼠标进入 / 离开、焦点获取 / 失去、选中 / 未选中、启用 / 禁用等。

参考资料


感谢张凯峰的策划以及审校。

给InfoQ 中文站投稿或者参与内容翻译工作,请邮件至 editors@cn.infoq.com 。也欢迎大家加入到 InfoQ 中文站用户讨论组中与我们的编辑和其他读者朋友交流。

2011-01-04 17:585255

评论

发布
暂无评论
发现更多内容

湖仓一体天花板,大数据一站式SQL分析技术实践

华为云开发者联盟

大数据 HetuEngine 湖仓一体 SQL分析 华为云FusionInsight

ADmobile首席架构师王威:广告业务云上运维最佳实践

阿里云弹性计算

阿里云 弹性计算 年度峰会

Apache Oozie学习笔记(一)

恒生LIGHT云社区

大数据 hadoop 工作流 调度

web技术分享| web的白板工具栏封装

anyRTC开发者

前端 Web 音视频 视频会议 白板

[转]注释驱动的 Spring cache 缓存介绍

kimmking

运维工程师必备利器|一招实现运维智能化!

云智慧AIOps社区

运维 AIOPS 运维工程师 基础知识 自动化运维

深入解析QUIC协议

拍乐云Pano

WebRTC RTC QUIC QUIC协议

前端开发代码区域规范分享

@零度

前端开发 代码规范

Towhee,开源的 embedding 框架与社区

Zilliz

数据库 开源 向量检索

开源走向世界(上):开源构建全球化的舞台丨BDTC 2021

PingCAP

据说有人面试栽在了Thread类的stop()方法和interrupt()方法上

华为云开发者联盟

高并发 crud Thread类 stop interrupt

如何使用JDBC API操作数据库

编程江湖

JDBC

CI/CD制作流程

wong

Docker jenkins ansible kubenetes

静态代理模式——时间都去哪儿了

蝉沐风

设计模式 代理模式

阿里云刘强:无影云电脑构建云上安全办公室

阿里云弹性计算

弹性计算 年度峰会 无影云电脑

vscode中Tasks及Emmet的应用

编程江湖

vscode

java学习中cookie原理

编程江湖

java 编程

定了!皮皮APP助力电子竞技游戏师职业技能标准发布!

联营汇聚

尚硅谷Docker与微服务实战教程发布

@零度

大数据 dokcer

自动驾驶训练如火如荼,网络带宽跟不上怎么破?

焱融科技

人工智能 自动驾驶 云计算 高性能 文件存储

一文带你看懂分布式软总线在家庭场景的应用

HarmonyOS开发者

HarmonyOS

网络安全好学吗?手把手教你学metasploit信息收集 网络安全工程师学习资料汇总

学神来啦

青藤:一招制敌!微隔离,让勒索软件不再横行

青藤云安全

利用闭包实现自定义等待方法

FunTester

多线程 并发测试 闭包 FunTester 自定义等待

后端老司机的跨域之旅

勇哥java实战分享

后端 CORS

java开发之内存模型面试分享

@零度

JAVA开发 Java内存模型

译文丨伯克利对serverless的看法:简化云编程

华为云开发者联盟

Serverless 云编程 伯克利 无服务器计算 云函数

Linux之文件属性详解

入门小站

Linux

元宇宙到家,那些「聪明」的设计工具

Zilliz

在线JSON转XML工具

入门小站

工具

大数据开发之Spark Shuffle 原理分析

@零度

大数据 spark

使用Dijit实现界面组件化开发_Java_成富_InfoQ精选文章