写点什么

构建前端 UI 组件的新思路

  • 2010-05-26
  • 本文字数:2841 字

    阅读完需:约 9 分钟

前端 UI 组件,目前流行的实现方式大多源自传统客户端的 UI 设计体系。无论是早期的 Bindows,还是近几年兴盛的 ExtJS,其 UI 组件都在模仿客户端软件,代码实现建立在复杂的继承体系上。好处是可以构建出和客户端体验一致的一整套 UI 组件,但弊端也很明显:组件长得都差不多,代码则继承太深,牵三挂四,不够轻便。

如何才能让前端 UI 组件轻便灵活起来呢?首先得意识到 Web UI 设计有自己的独特性。Web 页面可分为两种:一种是以展现信息为主的 Web 页面(web page),另一种是以操作信息为主的 Web 应用(web app)。对复杂的 Web 应用来说,可以采用 ExtJS 等类库来构建类客户端体验。但是,越来越多的 Web 应用已逐步脱离模仿客户端的阶段,开始从 Web 的独特性出发,将传统 UI 组件的功能融入到 Web 页面中。

举一个例子:国内的 Web 邮箱,无论是 163 还是 QQ 邮箱,都会让人联想起 Outlook 或 Foxmail 等客户端软件。这种模仿成本很高,然而带来的效果个人觉得却不是很好。反观 Gmail,整体 UI 设计,简明轻快,但功能一点也不逊色。在 Gmail 里,看不到 Tree,看不到 DataGrid,脱离了这些传统 UI 组件的框框,将功能融入到 Web 元素里,反而让用户更自然、更高效。

前端 UI 组件的 Web 特性,需要我们打破传统思维,换一个角度,重新去思考 UI 组件的基本组成要素。换什么角度去思考?什么角度才是合适的?这离不开具体场景。下面以一个实例来说明。

在淘宝页面中,以下是几个常见的 UI 组件:

传统思路里,我们会构建三个组件来实现:

  • Slide(轮播)组件
  • Tabs(标签页)组件
  • Carousel(旋转木马)组件

Tabs 组件,我相信任何一个前端开发工程师最多半天都能搞定,而且还能把延迟触发、动态加载等特性也给实现了。Slide 组件也不难。Carousel 组件看起来稍微复杂一些,但熬上一个通宵研究研究循环的实现,也不是什么大难题。

上面是传统思路。新思路是:我们真的需要分三个组件来实现吗?

仔细思索,我们可以提取出这三个组件的共同点:

  • 都有一组触点(Triggers)。 Slide 的触点是数字,Tabs 的触点是标签头,Carousel 的触点是小图。
  • 都有一组面板(Panels)。就是内容区域。
  • 通过触点可以让面板切换(Switch)。

上面的几条里有一个很重要的概念:切换。所有这些组件的共同点是可切换的。如果我们实现了一个可切换(Switchable)组件,上面三种组件就都是特例了。

根据上面的抽象,很容易实现 switchable.js:

复制代码
function Switchable(container, config) {
}
S.mix(Switchable.prototype, {
init: function() { }
switchTo: function() { }
next: function() { }
prev: function() { }
});

上面仅实现了基本的切换功能,我们可以进一步通过插件来实现更多功能:

  • plugin-autoplay.js – 提供自动播放功能。
  • plugin-effect.js – 提供切换时的各种特效。
  • plugin-circular.js – 提供循环切换功能,比如 Slide 自动播放到第 5 张时,能无缝循环播放到第 1 张。
  • plugin-lazyload.js – 提供数据的延迟加载功能。

插件的实现在 JavaScript 这种动态语言里是小菜一碟。至少有两种思路,一种是埋好钩子(hooks),插件根据钩子进行扩展:

复制代码
S.mix(Switchable.prototype, {
init: function() {
S.each(Switchable.Plugins, function(plugin) {
if(S.isFunction(plugin.init)) {
plugin.init();
}
});
},
switchTo: function(index) {
this.fire(‘beforeSwitch’, {toIndex: index});
}
});

在插件的代码里,定义好 init 方法,以及监听相关事件(事件可以看成是一类 hooks)即可实现需要增加的功能。

插件的第二种实现方法是动态修改基础对象,可以重写某些方法,也可以利用 AOP 特性,将增加的功能织入到基础对象中:

复制代码
S.weave(function() {
// plugin code
});
}, ‘after’, Switchable.prototype, ‘init’);

上面的代码表示在 Switchable 的 init 方法执行完成后,再紧接着执行 plugin code。

通过这种方式,我们无需用到任何继承概念,没有 super,没有 extend,利用 JavaScript 的原生动态语言特性,就比较完美地解决了问题。

从 Switchable 的角度看,上面三个组件可以描述为:

  • Tabs 是普通的 Switchable 组件。
  • Slide 是可自动切换且切换时有特效的 Switchable 组件。
  • Carousel 是可自动切换、切换有特效、可循环切换的 Switchable 组件。

来看下 Slide 的实现,变得非常简单:

复制代码
var defaultConfig = {
autoplay: true,
circular: true
};
Function Slide(container, config) {
config = S.merge(defaultConfig, config);
Slide.superclass.constructor.call(this, container, config);
}
S.extend(Slide, S.Switchable);

这里用到了类似 YUI 的 extend 方法,实现了继承,同时较好的保持了 JavaScript 的原汁原味。

可以看出,Tabs、Slide 和 Carousel 组件,彼此之间没有本质差别。封装出这三个类,仅仅是为了让开发者能方便快捷的调用(这些是高级 API)。对于资深开发者来说,在实例化 Switchable 时,通过传入不同参数即可实现所需效果(Switchable 是中级 API)。

更有意思的事情是,换一种思路实现代码后,也能帮助我们换一种思路看世界:

这个组件,可以看成是触点为图片的 Tabs 组件。左右两个翻页,无非是调用 next/prev 方法。进一步:

  • Tabs 组件可以看成是仅有基本切换功能的 Slide。
  • Slide 可以看成是触点悬浮在图片上面的 Tabs。
  • 等等等等

最后会发现,这三个组件,本身就是相通的。原本同一物,何必分开实现呢?我们可以得到一个结论:

只要符合 switchable 可切换特性的 UI 组件,原则上都可以通过 Switchable 实现。 唯一限制的是您的想象力!

比如,在 Switchable 的基础上,我们可以进一步实现 Album(画报),实现 CoverFlow(仿 iTunes 的封面切换效果)等等。

上面对 UI 组件的思维角度是可切换(Switchable),这是一个形容词。进一步思考,我们还可以得到以下形容词:

  • Draggable – 可拖曳的
  • Positionable – 可定位的
  • Selectable – 可选择的
  • Sortable – 可排序的
  • Stackable – 可堆叠的
  • Clickable – 可点击的
  • Ajaxable – 可 ajax 的

在这种新思路下,前端 UI 组件的基本组成要素已不是 Panel、Memu 和 Button 等传统 UI 基础单元,而是上面这些形容词。假设我们要实现一个可拖曳的可动态加载数据的可排序的表格时,或许像下面这样写一行代码就实现了:

复制代码
S.Widget(‘tableId’).draggable().ajaxable().sortable();

这是一个梦想。但有梦,去追,说不定就能实现。

备注

上面的代码里使用了 KISSY UI 类库,详细请参考: http://kissy.googlecode.com/

Switchable 的详尽代码实现请参见: http://kissy.googlecode.com/svn/trunk/src/switchable/


作者简介:王保平,前端架构师,网名射雕,花名玉伯。崇尚简洁而不简单,相信付出才有收获。就职于淘宝网 UED 部,忙并快乐着。个人博客: http://lifesinger.org/

本文已被收录在《架构师》(5 月刊)。

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

2010-05-26 04:2113458

评论

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

锻造船用发动机动力系统,铸强船舶“心脏”

DevOps和数字孪生

1周上线,2个月交付!有巢数智如何用 NocoBase 颠覆建筑行业数智化效率

NocoBase

开源 低代码 插件 数智化 建筑业

智慧环保系统(源码+文档+讲解+演示)

深圳亥时科技

AI Day引爆测试圈:你的测试工具该升级了,手工测试到AI智能测试

测试人

人工智能

ElevenLabs 33 亿美元估值的秘密:技术驱动+用户导向的「小熊软糖」团队丨Voice Agent 学习笔记

声网

智能扫描助力节碳超13万吨,合合信息旗下扫描全能王“指尖减碳”写就绿色诗篇

合合技术团队

人工智能 算法 OCR 扫描全能王 #大数据

百度百舸万卡集群的训练稳定性系统设计和实践

百度Geek说

集群 AI 搜索引擎

反向海淘代购独立站的“神器”——集运功能到底怎么用?一篇给你讲明白!

代码忍者

邀请函丨就在下周,相约新乡!AI驱动数字化创新设计促进新质生产力发展交流会

Altair RapidMiner

AI 数字化转型 制造业 仿真 CAE

1688拍立淘图片搜索接口全攻略

tbapi

1688API 1688图片搜索接口 1688拍立淘接口 1688图片识别接口 1688图搜API

Hologres实时湖仓能力入门实践

阿里云大数据AI技术

大数据 数据仓库 OLAP hologres

腾讯云HAI | 1分钟,1元成本带你快速搭建你的专属DeepSeek,还有多场景体验操作指导

六月的雨在InfoQ

腾讯云 DeepSeek HAI应用服务器 Chatbox

突破续航瓶颈:数字样机技术引领新能源汽车复合制动新方向

DevOps和数字孪生

写好代码之对象的创建

x-arts

设计模式 写好代码 代码治理

C#进阶-ASP.NET网站会话固定漏洞的解决

Damon小智

C# asp.net 网络安全 漏洞 会话固定

什么是有限元分析技术?仿真软件正逐步成为新型科技

思茂信息

仿真 abaqus 有限元分析

数据线良率总上不去?MES系统教你3招把不良率砍半!

万界星空科技

数字化 mes 万界星空科技 制造业工厂 数据线工厂

1688店铺所有商品列表接口全攻略

tbapi

1688API 1688店铺所有商品接口 1688店铺商品采集

企业想知道,DeepSeek如何从外力变内力?

脑极体

AI

SvelteKit 最新中文文档教程(4)—— 表单 actions

冴羽

前端 前端开发 前端框架 Svelte SvelteKit

Markdown 模板变量的使用

NocoBase

开源 markdown 低代码 零代码 无代码

使用 INFINI Gateway 保护 Elasticsearch 集群之修改查询不合理参数(二)

极限实验室

elasticsearch Gateway

芯盾时代身份管理解决方案

芯盾时代

iam 统一身份认证 统一身份管理

社区动态 | KWDB 体验官火热招募中

KaiwuDB

招募 数据库、 KaiwuDB

如何通过 Apache SeaTunnel 实现 MySQL 到 OceanBase的数据迁移同步

Apache SeaTunnel

AI口语陪练APP的核心功能

北京木奇移动技术有限公司

软件外包公司 AI口语练习 AI英语学习

数智化转型不是“买硬件”,DeepSeek一体机别乱上

Alter

智能化采购:中烟创新如何利用编审系统提升文件编制与审核效率?

中烟创新

Flink CDC+Hologres高性能数据同步优化实践

阿里云大数据AI技术

大数据 flink 数据仓库 OLAP hologres

真正的AI新风口,竟是它?

Immerse

构建前端UI组件的新思路_Java_王保平_InfoQ精选文章