ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

ZK框架ZUL、ZHTML与Native组件区别与实战选型指南

ZK框架ZUL、ZHTML与Native组件区别与实战选型指南 接手过ZK框架项目的朋友多半都经历过这个困惑明明是一个zul文件里面一会儿写window、button一会儿又直接写div、table甚至还有native:xxx这种看起来就不太一样的标签。这三个东西到底有什么区别什么时候该用哪个我最早接触ZK的时候也在这上面栽过跟头后来翻了官方文档、看了源码、又在实际项目里反复折腾才算把这件事彻底理清楚。这篇博客就把我积累的结论完整写出来。不管你是刚接触ZK的新手还是已经写了一阵子但一直没搞明白的兄弟看完之后应该都能搞清楚ZUL组件、ZHTML组件、Native组件三者之间的本质区别、适用场景和混用技巧。1. 这三个词到底指什么先理清概念源头很多人的困惑其实是从概念层面开始的因为这三个词压根不是同一个分类维度下的东西。要搞清楚区别得先知道它们各自的出身。1.1 ZUL是ZK框架的“母语”不是某一种具体的组件ZULZK User Interface Language是ZK框架自己定义的一套XML标记语言后缀为.zul的文件就是用这种语言写的。在ZUL文件里你可以写window、grid、listbox等ZK核心组件标签也可以写各种扩展组件。关键点在于ZUL是整个页面的载体。服务器收到.zul请求后ZK会解析这个XML结构每个标签对应实例化一个Java组件对象这些对象组成一棵组件树最终被渲染成HTML发送到浏览器端。所以严格来说ZUL不是一种组件而是一系列组件共同生活的环境。我们平时说的ZUL组件通常指代的是ZUL这种标记语言下的标准ZK组件比如Button、Textbox、Grid、Window这些。1.2 ZHTML组件披着HTML外衣的ZK组件ZHTML组件看名字就知道是ZK提供在org.zkoss.zhtml包下面的组件集合。这个包里的组件类和HTML标签有很强的对应关系——div对应Div组件、span对应Span组件、table对应Table组件、img对应Image组件还有A、Ul、Li、Input等等。和标准ZK组件相比ZHTML组件有个特点它长着HTML标签的样子但是拥有ZK组件的血统。什么叫拥有ZK组件血统就是你可以给它加id、可以给它挂事件监听器、可以动态创建和销毁、可以放在ZUL组件树里和其他ZK组件一起工作。从使用感受来说ZHTML组件像是ZK框架为了兼容HTML习惯而做的一层翻译——用HTML的语法书写但背后还是组件对象那一套运行机制。1.3 Native组件真正意义上的“直通车”Native模式是ZK提供给开发者的一种特殊写标签的方式语法是在zul文件里使用native:xxx形式的标签或者在文件头声明对应的命名空间。?native? div这里是native模式输出的纯HTML/div在Native模式下标签内容完全不会经过ZK组件的实例化过程而是被当成纯文本/纯HTML直接输出到响应流里。这意味着你不能给它设置id即使设置了也不会生成对应的组件对象、不能直接挂ZK事件监听、不能参与组件树的管理。用一个通俗的比喻ZUL组件是精装修的样板房各种内部结构和功能都给你配好了ZHTML组件是买毛坯房自己简单布置有基本的主体结构但内部装修组件行为得自己处理Native组件是直接在空地上搭帐篷怎么搭完全是你自己的事你获得的是最大的自由同时也失去了建筑主体提供的所有支撑。2. ZUL组件为什么ZK官方始终推荐它ZK框架的核心卖点就是服务器端UI。ZUL组件体系是整个框架的基石也是官方文档和各类教程中占比最多的部分。我建议所有项目以ZUL组件为主原因在于它背后一整套成熟的生命周期管理和事件机制。2.1 组件生命周期从XML标签到Java对象ZUL文件里的每个ZK组件标签在服务器端都会经历一个完整的生命周期解析XML节点 → 创建对应的组件Java实例 → 将标签属性映射为组件属性 → 挂载到组件树 → 渲染输出。这个生命周期极其重要。因为ZK采用的是一种以服务器为中心的UI模型浏览器端维护的只是一个状态快照真正的业务逻辑、页面状态都保存在服务器端的组件树里。你在ZUL里写一行button label提交/服务器会创建一个Button对象用户点击这个按钮请求发到服务器框架找到对应的Button对象并触发的onClick事件。整个过程都是组件化的。2.2 事件监听和数据绑定的威力ZUL组件最让人省心的地方在事件处理。以一个实际场景为例我要做一个用户列表的删除功能grid iduserGrid model${vm.userList} columns column label姓名/ column label操作/ /columns template namemodel row label value${each.name}/ button label删除 onClickcommand(deleteUser, userIdeach.id)/ /row /template /grid只写一个onClickcommand(deleteUser, userIdeach.id)点击事件就会通过ZK的事件总线传到后台ViewModel对应的Command方法里参数也自动带过去了。这种开发效率是原生HTMLJavaScript那套方式没法直接比的。还有一个重要优势是动态UI控制。ZUL组件可以在服务器端通过Java代码动态创建、移出、隐藏Window win new Window(); win.setTitle(动态窗口); win.setWidth(400px); win.setClosable(true); page.appendChild(win);又或者通过EL表达式和MVVM模式让页面的某个区域根据模型状态自动变化。这在原生HTML里需要写一堆DOM操作在ZUL里就是加一行visible${vm.showPanel}的事。2.3 为什么不能什么场景都用ZUL组件这里有个我正在提醒你注意的边界ZUL组件数量多、功能全但不代表越复杂的组件越适合你的场景。比如你想渲染一大段带格式的静态说明文字、一个由前端框架生成的图表容器、或者一段必须保持原样输出的第三方代码片段这时候硬要用ZK组件去模拟反而很别扭。类似Grid这种重组件数据量大时动辄上千行ZUL会为每一行渲染生成Java对象和组件节点内存开销非常大。这也是为什么我在一些轻量展示型项目里反而会偏向ZHTML或者Native——它们能让你在享受部分ZK优势的同时避开重量级组件的性能负担。3. ZHTML组件在ZK页面里借用HTML标签的灵活手段ZHTML组件存在的意义是让熟悉HTML的开发者能以几乎零成本的方式融入ZK体系。它在你不想用重量级ZK组件、又不想完全脱离组件体系的时候是一个很好的折中方案。3.1 ZHTML组件的真实模样在zul文件里你可以直接写这样一段div idinfoBox stylepadding:10px;border:1px solid #ccc; span stylecolor:red;注意/span 这里是ZHTML组件 /div看起来就是普通的HTML对吧。但在ZK的运行时里div被解析成了一个org.zkoss.zhtml.Div对象span被解析成了org.zkoss.zhtml.Span对象。这意味着可以在div标签上写if、forEach等ZK属性动态控制是否渲染可以调用div.setStyle()、div.setVisible()等Java方法操作它可以给它添加onClick事件监听器在服务器端处理点击逻辑。最有用的一个点是在ZHTML组件里嵌入EL表达式。比如在页面上展示当前用户名div 当前登录用户span stylefont-weight:bold;${sessionScope.currentUser.name}/span /divZK引擎渲染ZHTML组件时会主动解析其中的EL表达式并替换成实际的服务器端数据。这个特性在构建混合页面的场景中非常重要——你不需要把整块内容做成一个数据复杂的ZK组件只需要在某些关键点位插入动态数据即可而且完全不需要写JavaScript。3.2 ZHTML组件与ZUL组件如何协作ZHTML组件可以和ZUL组件放在同一个组件树中甚至可以互相嵌套。window title用户中心 bordernormal width600px div stylemargin-bottom:10px; 欢迎回来span${sessionScope.currentUser.name}/span /div grid model${vm.userList} !-- 这里是标准ZK组件 -- /grid div stylecolor:gray;font-size:12px;margin-top:5px; 提示本页统计时间为${serviceTime} /div /window在这个例子里div和span属于ZHTML组件window和grid属于标准ZUL组件它们相处得很融洽。ZK在渲染时会遍历整棵组件树把不同性质的组件正确输出为对应的HTML片段。我实际项目里最常用的组合方式是整体布局用ZUL的Window/Borderlayout等容器内容区里用ZHTML做段落排版和轻量结构只有真正需要交互逻辑的按钮、输入框才用ZUL的Button、Textbox等重量级组件。这种组合方式页面结构清晰又不至于整棵树全是重组件。3.3 ZHTML组件的局限性和兼容性注意点ZHTML组件虽然好用但它不是万能的有几个点需要留意。服务器端语法支持有限不是所有HTML5标签在ZHTML包里都有现成的组件类。比如header、footer、article这些语义化标签老的ZHTML包里是没有对应类的ZK会把不认识的前置标签当作未知元素处理或者直接报错。新版本虽然有所扩充但也做不到和HTML5完全同步。事件支持有部分差异ZHTML组件支持onClick这类常见事件但像onInput、onChange这类在原生HTML标签上的事件并不是所有ZHTML组件都内置支持的。遇到不支持的你还是得回归到ZK组件或者Native前端JS去处理。渲染属性不完全等同于浏览器行为ZHTML组件生成的HTML代码虽然看起来和原生HTML一致但因为它经过了ZK组件的渲染管线个别属性的兼容性处理会和直接写HTML有一点点差异主要体现在一些新版属性的透传上。总体来说ZHTML组件适合需要有HTML的轻便、又想利用ZK动态能力的场景。如果你不依赖ZK的服务器事件和动态更新能力只是想原样输出一段HTML区块那么下面要说的Native模式才是更省事的选择。4. Native模式绕过ZK引擎直接输出HTML的代价与回报Native模式是三种方案里最特殊的存在。它不是我前面说的组件类型而是ZK提供的一种不把标签解析成组件的通道。使用方式有以下几种4.1 Native模式的两种用法第一种是在zul文件中直接声明?native?指令让整个文件中的HTML标签不经过组件管线直接输出?page title纯HTML输出页面? ?native? html body h1这一段是原样输出的HTML/h1 p没有任何ZK组件而是纯HTML./p /body /html第二种是使用native标签包裹代码块实现局部Nativewindow title混合页面 bordernormal width500px button label这是ZK组件按钮/ native:div 这一段div内的内容不会生成ZK组件对象直接输出HTML。 input typetext namerawInput/ /native:div button label这也是ZK组件按钮/ /window局部Native模式下native:div内部的所有内容都是原样输出不会生成任何组件对象也没有id、没有事件监听能力。4.2 Native模式最大的价值性能与自由经常有人问我为什么ZK都提供了ZUL组件这么好用的东西还要搞一个Native模式出来我自己用下来Native模式的价值主要体现在两方面。性能上。ZUL组件的强大是有代价的每一个组件在服务器端都是一个Java对象对象越多内存越大渲染越慢。当一个页面包含大量静态HTML内容时比如一个数千字的说明书页面如果全部用ZUL组件或ZHTML组件会白白在服务器端生成成百上千个组件对象拖慢整体响应时间。而Native模式把这些内容当成普通文本直接写入输出流服务器端几乎零成本。这里给个我实际优化过的案例。之前做一个后台管理系统的帮助中心模块页面里有大段的排版说明文字、图片链接和注意事项列表原本是从数据库动态读取后拼到ZUL里渲染导致每次请求都要创建上千个标签对象接口响应在2秒左右。后来我把静态内容改成读取后用Native模式输出响应时间直接降到200毫秒以内。效果立竿见影。自由度上。Native模式下你可以在ZK页面里随意嵌入第三方前端代码比如一个地图组件的iframe、一个图表库的配置脚本、一段需要保持原样的HTML模板。这些内容如果用ZHTML组件可能会因为ZK对属性的解析导致部分属性丢失或者被改写而在Native模式下是完全透明的写什么浏览器接收什么。4.3 Native模式的代价你得直面三个问题Native模式这么好用代价是什么实际操作中主要是三个问题。第一个问题是无法在服务器端控制Native区域内的内容。Native块里的内容在渲染时是原样吐出去你没法在服务器端通过Java代码去设置某个span的style也没法动态修改内部的文本。如果需要动态数据你只能在输出前用字符串拼接完成或者在Native块里使用EL表达式ZK在Native模式下部分版本支持EL解析但这依赖版本不一定可靠。第二个问题是没有事件机制。在Native区域内你写的onClickdoSomething()是原生JavaScript的事件绑定不会触达ZK的服务器端事件。想在Native区域里实现交互逻辑你得自己写前端JS、自己发AJAX请求彻底脱离服务器端组件模型。第三个问题是安全性。因为Native内容会原样输出如果你在里面拼接了未经转义的用户输入内容XSS漏洞的风险就比ZUL组件高很多——ZK组件在渲染文本时往往会做转义处理Native模式则完全不做。所以Native模式适合的是我只想原样输出HTML片段、不需要服务器交互的场景。它和ZHTML组件的关系更像是我愿不愿意为这块内容保留一个服务器端对象的选择。5. 三种写法的选择边界与混合使用方案把三种方式的具体用途和边界搞清楚以后最关键的实操问题是在一个具体页面里到底该怎么选需要什么就选什么还是有更优的思考路径5.1 选择决策表按需求类型快速定位我把平时做技术方案时自己用的一张决策表整理在下面大家可以直接对照查询。需求场景推荐方案核心理由主要业务交互逻辑、表单提交、数据列表增删改查ZUL组件有完整的事件机制、MVVM绑定、动态UI控制能力静态展示区块、排版段落、带样式的说明文字Native模式零组件开销渲染快不污染组件树需要EL表达式动态插入数值、又不想用重组件ZHTML组件支持EL解析和部分动态属性比Native更了解ZK生态嵌入第三方前端代码地图、图表、富文本Native模式原样输出不会被ZK改写兼容性最稳妥需要动态创建、销毁、隐藏、移动的内容ZUL组件ZUL组件的生命周期管理才是这类需求的正解大量静态列表行渲染、前端MVVM式的局部更新混合外层ZUL组件 内部Native/ZHTML降低组件树规模提高渲染效率5.2 一个让我纠结很久的选择ZHTML还是Native很多开发者最难判断的是ZHTML和Native选哪个。我给出一套自己的判断标准帮大家少走弯路。需要写在页面里的内容请你先问自己三个问题这一段东西是否需要在服务器端动态改变这一段东西是否需要事件回调到服务器端这一段东西是否需要被ZK组件树管理如条件渲染if、遍历forEach三个问题里有任何一个答案是需要就选ZHTML组件。因为ZHTML虽然比Native重一点但保留了服务器端的操作能力。三个问题答案都是不需要那就直接用Native。既然只需要原样输出没必要让ZK为每个标签都生成一个Java对象。举个例子我要渲染一段产品介绍里面的用户评价内容是数据库动态查询的并且希望在点击展开按钮时再加载。这段内容更适合用ZHTML。如果只是渲染一段固定的免责声明大概率用Native就够了——甚至可以直接放到静态HTML文件里让ZK页面通过include引入连解析都不需要。5.3 混合方案的封装思路如何优雅地在ZK页面里嵌Native内容混合使用的时候一个常见问题是Native块和ZUL组件之间没有明确的边界感代码容易乱。我分享一下自己的处理方式。我习惯把Native内容封装成ZK的自定义组件或宏组件对外暴露几个可配置的属性。这样页面里只看到div元素的组件调用底层具体是用Native还是ZHTML实现的页面的编写者完全不用关心。比如我做了一个代码高亮块的宏组件Highlightercomponent namehighlighter inline native:div pre classbrush:${arg.lang};${arg.code}/pre /native:div /inline /component页面上就可以这样优雅地使用highlighter langjava code${vm.exampleCode}/这里code参数在服务器端赋好值Native块里的EL表达式会被替换剩下的高亮逻辑完全交给前端的Prism.js脚本处理。这样既享受了Native的性能优势又保持了组件化开发的清爽感。5.4 合理使用Limit保持ZK后台的可维护性说到混合使用我最后还要提醒一个特别容易踩的坑不要把所有HTML元素都改成ZHTML组件。我在前面提过ZHTML组件确实和HTML长得像但有个无聊又实际的麻烦——它的显示效果和样式规则不一定和原生HTML完全一致。比如table布局如果你在zul文件里新写一个table它是ZHTML里的Table组件那么ZK可能会据此生成额外的一些标签属性。这些属性在大多数情况下和原生HTML看起来没区别但在某些特定的CSS框架下比如Bootstrap的某些版本多出来的空属性或默认属性就可能导致样式错位。所以我的建议是能用Native输出静态结构就不用ZHTML能用ZHTML方便地省去ZK重组件就不用ZULZUL组件只留给真正需要的场景。这个能轻则轻的原则能让ZK项目的性能和维护性都提升一个档次。6. 实战中踩过的坑与排查经验最后聊聊我在实际开发中用这三种方式踩过的坑以及对应的排查思路。这些内容在官方文档里不太容易找到都是一步一步试出来的经验。6.1 ZHTML组件用了id但服务器端却找不到对应组件这是ZHTML组件使用中最容易踩的坑之一。你明明在zul文件里写了一个span idmySpan但后台执行Components.path(page, mySpan)或者page.getFellow(mySpan)时得到的结果可能是null。这个问题的根因在ZK对组件ID和组件对象的管理方式上。ZHTML的id属性在ZK解析时只有组件被特殊处理时才会注册如果你只是写了一个静态的span idmySpan解析器可能不会像标准ZUL组件那样主动加入ID映射表。检查的时候首先要确认你用的是getFellow(mySpan)还是getFellow(mySpan, true)其次要确认渲染时ZHTML组件的idable属性是否生效。这个行为在不同ZK小版本上表现还不完全一致我见过5.x和9.x的表现就不太一样。解决方案有两个第一别指望裸写的ZHTML组件都能被getFellow()找到——如果你确实要通过ID控制这个元素可以考虑把id作为ZHTML组件的属性显式绑定第二更稳妥的做法是对需要控制的元素选择ZUL组件比如label或textbox放弃ZHTML的HTML式写法。6.2 Native区域内的按钮点击后ZK完全没反应另一个高频问题是在Native区域里写了button onclickdoSomething()然后doSomething函数里只是简单调用了ZK的zAu.send()结果发现服务器端完全没有收到事件。这个问题的核心是Native区域本身就是绕开ZK引擎的。你在Native里写的所有属性都不会经过ZK事件绑定服务器端根本不知道那里有一个可以接收ZK事件的组件。所以你在Native区域里发起的请求不能使用ZK的服务器端事件机制来响应。正确的处理方式是用ZK自带的前端API比如zkau相关的AJAX请求或者自己写一个普通的HTTP请求到某个Servlet/后端接口。如果确实想利用ZK的MVVM和事件机制那么请不要把交互元素放到Native区域中——要么把按钮挪到Native外面作为ZUL组件要么将该区域改用ZHTML组件以保留ZK事件能力。6.3 动态生成了ZHTML组件但样式和布局错乱还有一次我在代码里用Java动态创建了一批ZHTML组件放在一个Grid容器里结果页面上出来的效果很诡异表格的对齐方式全乱了本该在单元格A里的内容跑到了单元格B里。排查后发现问题出在我动态创建ZHTML组件时只设置了组件属性和文本内容没有指定布局所需的CSS类名。ZK在渲染ZHTML组件时不像标准ZUL组件那样会自动生成完整的样式体系。ZHTML组件的样式基本是裸奔的需要你自己加类名或者内联样式。另外要注意的是ZHTML组件虽然名字里带HTML但它并不等于浏览器里的HTML元素。每次通过Java代码动态创建ZHTML组件时都应该检查一下它是否真的挂载到了正确的父组件下以及是否继承了必要的ZK样式类。遇到复杂布局要实现更好的选择依然是标准ZUL组件尤其是Grid、Hbox、Vbox这些自带布局能力的组件。6.4 EL表达式在Native区域失效的版本差异这个坑比较隐蔽。某些ZK版本下你在native:div内容里写的${sessionScope.currentUser.name}确实会被ZK引擎解析但我遇到过在个别版本中这种EL表达式在Native区域中完全不会被解析导致页面上直接显示出了原始的EL表达式字符串。为什么会这样因为Native模式的本质是跳过ZK组件解析而EL表达式解析是ZK渲染管线的一部分当内容被当作普通文本处理时解析动作是否执行就取决于版本实现和配置了。这个行为不一致的问题在ZHTML组件中是不存在的——ZHTML组件作为ZK组件体系的成员会正常走EL表达式的解析流程。为了避免踩这个坑我的建议是如果你确定某些动态值需要在页面里展示那么用ZHTML组件而不是Native模式。如果是纯静态内容必须用Native那么就把动态值的替换逻辑提前到Java代码中比如在组装内容时就完成字符串替换不要依赖ZK渲染时的EL解析。写到这里ZUL、ZHTML、Native这三者的区别和选型思路基本说清楚了。说句实在话这三样东西本身没有优劣之分关键还是看你的项目场景需要什么。我个人的组合习惯一直是业务主体交给ZUL组件静态展示和第三方嵌入交给Native需要轻量动态能力但又不想上重组件的地方用ZHTML。这个组合让我在性能和开发效率之间找到了一个平衡点。如果你正在做ZK项目不妨也按这个思路重新审视一下自己页面里的组件使用说不定能挖出一些不必要的性能损耗。
返回列表