深入学习 SAP UI5 框架代码系列之二:UI5 控件的渲染器
在 Jerry 本系列的前一篇文章 深入学习 SAP UI5 框架代码系列之一:UI5 Module 的懒加载机制,我们已经了解到 UI5 Button Module 之一,ButtonRenderer,专门负责将 sap.ui.commons.button 的实例数据,渲染成原生的 HTML 代码。
为什么要引入控件渲染器这个中间层呢?
我们看看 SAP UI5 应用一个典型的 xml 视图的源代码:
不难发现,源代码中位于命名控件 sap.m 下面的控件标签,如 App, pages, Page, Panel 这些例子,都并非 HTML 标准控件,因此无法被浏览器识别,必须要有一个中间层,将其转换成浏览器可以支持的 HTML 原生标签才行。
在 ButtonRenderer.render 函数里设置断点,然后 F5 刷新页面,断点触发,就可从调用栈观察到 RenderManager 是如何调用 ButtonRenderer 执行渲染工作的。
下图画得有些乱,意图是想表达,最终渲染出的 HTML 源代码里的 button 标签的各个属性,分别是由 ButtonRenderer 哪一行代码实现的。
Jerry 刚刚做 SAP UI5 开发时,了解到 Renderer 机制后,心里有个疑问,SAP UI5 怎么知道 button 控件的渲染器是 ButtonRenderer。换言之,SAP UI5 控件和其渲染器之间的一一对应关系是如何维护的?
SAP UI5 框架里,每类控件都各自维护了一份 Metadata(元数据),其中有个 getRenderer 方法,返回控件对应的渲染器名称。
关于 SAP UI5 控件元数据,本系列后续文章会介绍。
从调试器里能观察到 button 控件元数据里,变量_sRendererName 维护了 button 渲染器的名称:sap.ui.commons.ButtonRenderer. 然而,这个变量在何时赋的值?
从下图第 42971 行能够看出:控件的渲染器满足命名规范:<控件名称>+ "Renderer", 一个简单的字符串拼接操作。
RenderManager 在哪些时刻会开启控件的重绘?
让我们对脚手架应用里的 button 点击事件处理函数稍作修改:每次点击按钮时,调用 setText 修改 button 的 text 属性:
点击按钮,发现 ButtonRenderer.render 再次被触发。
原因在于,oButton1.setText 最终会调用 button 原型链上的 ManagedObject.setProperty 方法,该方法内部有一个显式的 invalidate 调用。
如果忘记了 SAP UI5 控件的原型链设计,可以查看 Jerry 之前的文章:深入学习 SAP UI5 框架代码系列之一:UI5 Module 的懒加载机制。
Control.invalidate 内部经过计算,会得出当前页面需要重绘的区域,最终调用 RenderManager 进行重绘。
我们再来简单了解下 Angular 里的控件绘制。以 SAP Spartacus 的产品转盘(Product Carousel)显示控件为例: 最畅销的产品共有 12 款,分多屏显示在转盘控件里,每屏显示若干个产品。通过控件提供的左右箭头,进行屏与屏之间的切换。转盘底部的小红点,表示当前转盘显示的是第几个屏幕的数据。
SAP UI5 也能实现类似的复合控件,官方称呼为 Custom Control.
Spartacus 产品转盘控件的 HTML 代码表现形式为标签 cx-product-carousel,内部重用了另一个自定义标签 cx-carousel:
当前显示在屏幕里的产品信息,通过 cx-carousel 标签里三个 class 为 item active 的 div 标签显示。
这个自定义产品转盘控件通过 Spartacus 里的 Angular Product Carousel Component 实现。
Product Carousel Component 的 layout 实现里,将 Component 自身的属性 items作为输入,传入另一个 Component cx-carousel, 让其将属性值 title.
因为 cx-carousel 是一个可重用控件,除了显示产品转盘外,还可以用于显示其他同类实体的转盘显示,比如折扣转盘,促销活动转盘等等。因此,除了将 items传入 cx-carousel 之外,还需要告知后者,在转盘内部,以何种布局逻辑显示转盘的每一个元素。
因此,下图第九行通过<ng-template>标签定义了一个 id 为 #carouselItem 的模板,将此 id 一并传入 cx-carousel. 这样,转盘控件在运行时,针对转盘数据源 items$内存储的每一个产品数据,就会按照此模板定义的布局,进行绘制。
当初 Jerry 学习 Spartacus 这个产品转盘的设计时,觉得很亲切,因为其设计思路和 SAP UI5 List Binding(Aggregation Binding)是一致的。
SAP UI5官网上讲解 List Binding 的一个例子:
有一个 companies JSON 数组:
将 companies 路径传入 List 控件,完成了数据源的指定,通知 List 去绘制 companies 数组里的数据。具体渲染哪些数据?List 不知道,需要 items 子控件来定义,比如子控件的 title 属性,显示 JSON 数组的 name 字段,description 属性,显示 JSON 数组的 city 字段。List 会根据 JSON 数组里的 company 节点的个数,动态创建对应数目的 items 子控件。
这里的 SAP UI5 items 子控件,扮演的就是本文之前介绍的 Spartacus 产品转盘控件页面里,用<ng-template>定义出的 id 为 #carouselItem 的模板同样的角色。
感谢大家阅读。本系列下一篇文章:HTML 原生事件 VS SAP UI5 Semantic 事件,我会给大家分享,为什么 SAP UI5 要引入 Semantic 事件即语义事件的概念,而不直接使用 HTML 原生的事件。
版权声明: 本文为 InfoQ 作者【Jerry Wang】的原创文章。
原文链接:【http://xie.infoq.cn/article/8417523fade5ccda936c50d42】。
本文遵守【CC-BY 4.0】协议,转载请保留原文出处及本版权声明。
评论