写点什么

构建前端 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:2113240

评论

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

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

NocoBase

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

AI口语陪练APP的核心功能

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

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

商家域稳定性建设之原理探索|得物技术

得物技术

故障隔离与防护架构设计

FunTester

垂域大模型时代 | 专业数据铸就行业智能底座

数据堂

人工智能 金融 数据集 大模型 垂域模型

无人值守收入核算,无需下班的“AI收入会计”

用友智能财务

AI 数字化 财务 会计

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

tbapi

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

从协作视角看银行 SRE 转型:与虚拟 IT 组织的创新联动

嘉为蓝鲸

AIOPS SRE 银行运维

AI口语机器人的功能规划

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

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

DDNS和内网穿透服务实名监管趋严,贝锐花生壳成唯一可靠选择!

科技热闻

这款流行 AI 工具被盗用挖取加密货币,这些隐患你需要知道

阿里巴巴云原生

阿里云 云原生 Higress

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

DevOps和数字孪生

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

沉浸式趣谈

《Operating System Concepts》阅读笔记:p359-p388

codists

操作系统

DeepSeek黑科技加持 嘉为蓝鲸WeOps V5.0让运维预见未来

嘉为蓝鲸

智能运维 产品发布 嘉为蓝鲸 #WeOps

数据安全,信息安全解决方案,信息安全实施方案

金陵老街

信息安全 数据安全

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

万界星空科技

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

Cloud Ace 宣布成为 Langfuse 亚太地区首个代理商,提供 LLM 全链路方案

Cloud Ace 云一

LLM LLMOps 生成式 AI 应用 Langfuse

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

DevOps和数字孪生

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

深圳亥时科技

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

百度Geek说

集群 AI 搜索引擎

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

测试人

人工智能

运维人的福音!嘉为蓝鲸OpsPilot V3.2 联网检索功能,让问题解决快人一步!

嘉为蓝鲸

智能运维 产品发布 嘉为蓝鲸 #WeOps

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

阿里云大数据AI技术

大数据 flink 数据仓库 OLAP hologres

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

芯盾时代

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

Hologres实时湖仓能力入门实践

阿里云大数据AI技术

大数据 数据仓库 OLAP hologres

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

思茂信息

仿真 abaqus 有限元分析

试验协同管理平台(源码+文档+讲解+演示)

深圳亥时科技

智能车间管理系统(源码+文档+讲解+演示)

深圳亥时科技

每月仅能使用80小时,ToDesk再度降低免费用户使用时长

科技热闻

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