介绍企业级前端开发领域,Web 应用的扩展点预埋最佳实践
在企业级前端迭代越来越频繁的今天,开发者希望既能快速交付 MVP,又能在后续迭代里无痛插拔功能。ThoughtWorks 技术雷达长期倡导的 evolutionary architecture
思想,鼓励在核心代码里为未来变化预埋“可变点”。SAP UI5 社区多年来实践出两条主干:在 XML 视图里放置 extension point
,在控制器里暴露 extension hook
。这两种机制让应用像乐高一样可插可拔,同时保持与 SAP 官方升级兼容。本文结合 Radar 的“Adopt / Trial / Assess / Hold” 四象限视角,拆解如何在项目伊始就种下可扩展的“钩子”,并提供可直接运行的代码片段供读者上手。
技术雷达视角下的可扩展设计
ThoughtWorks 在 Radar Vol 32 所列出的 Design for Extensibility
归于 Tools — Adopt 象限,强调在框架层面预留插槽以减轻分叉带来的维护成本 (Tools | Technology Radar - Thoughtworks)。SAP UI5 的 extension point / hook
正是落地这一理念的典型实现:视图层用 declarative 的标记告诉框架“此处可替换”,控制器层用约定命名的函数让消费者选择性覆写 (Developer Adaptation - SAPUI5 Flexibility - SAP Help Portal, View Extension)。当后续业务需要插入按钮、表单或整段逻辑时,二次开发者只需在 manifest 里声明替换关系,无须改写源码,这与 Radar 所推崇的“可演进架构”不谋而合 (Build Your Own Technology Radar | Thoughtworks United States)。
XML 视图里的 extension point 实践
基本语义
在 XML 视图文件中,只要用 <core:ExtensionPoint name='...'/>
(也可用 JS API sap.ui.extensionpoint
)标记一个位置,就等于告诉 UI5“这里是一块可被替换的占位符” (View Extension, View Extension - SAP Help Portal)。运行时框架会在加载组件时查找 manifest 里的 sap.ui.viewExtensions
,决定是否把自定义片段或子视图注入该位置。若无匹配配置,占位符可显示默认内容或保持为空 (Implementing View Extension, Modification, and Replacement)。
可运行示例
下面示例基于最低 UI5 版本 1.108,在本地 ui5 serve
即可启动。代码里刻意使用单引号或反引号,避免违背本文格式约束。
Main.view.xml
代码语言:xml复制<mvc:View
xmlns='sap.m'
xmlns:mvc='sap.ui.core.mvc'
xmlns:core='sap.ui.core'>
<VBox>
<Text text='Product List'/>
<core:ExtensionPoint name='ProductActions'/>
<List items='{path: `/Products`}'>
<items>
<StandardListItem title='{ProductName}'/>
</items>
</List>
</VBox>
</mvc:View>
manifest.json(片段插入)
代码语言:json复制{
'sap.ui5': {
'componentName': 'demo.ext',
'contentDensities': { 'compact': true, 'cozy': true },
'viewExtensions': {
'demo.ext.view.Main': {
'ProductActions': {
'className': 'sap.ui.core.Fragment',
'fragmentName': 'demo.ext.fragment.ProductToolbar',
'type': 'XML'
}
}
}
}
}
ProductToolbar.fragment.xml
代码语言:xml复制<OverflowToolbar
xmlns='sap.m'>
<Button text='Add' type='Emphasized' press='.onAdd'/>
<ToolbarSpacer/>
<Button text='Download' icon='sap-icon://download' press='.onExport'/>
</OverflowToolbar>
把片段文件放到 /webapp/fragment
目录,再启动应用,就能看到按钮无缝注入,原视图无需改一行代码。这种做法正切合 Radar 推荐的“在设计阶段就暴露插槽”模式,让后续团队可以 Trial 或 Adopt 新功能而不破坏主干。
深入细节:默认内容与多聚合
ExtensionPoint 允许包裹默认控件,当无自定义实现时展示默认 UI,以提升开箱可用性 (View Extension)。对 sap.m.Table
这类二维聚合控件,需要同时在 columns
与行模板 cells
里各放一个占位符,以支持列级与行级注入 (View Extension)。UI5 1.38 以后甚至支持在占位符级别替换整视图,以应对大型功能替换 (1.38.63 - Demo Kit - SAPUI5 SDK)。
控制器里的 extension hook 策略
概念与执行顺序
Controller 扩展以 sap.ui.controllerExtensions
节点为中心。框架在运行期把自定义 controller 与标准 controller 做对象级合并,而生命周期方法 onInit / onAfterRendering 等保持链式调用,确保标准逻辑先执行,再执行扩展逻辑 (Controller Extension - SAP Help Portal)。若想在特定步骤插入自定义算法,可在原 controller 暴露一个前缀为 extHook
的空函数;二次开发者只需实现同名方法并在扩展 controller 中返回,框架便会在合适时机调用 (Using Controller Extension - Documentation - Demo Kit - SAPUI5 SDK, How to invoke hook extension in controller extensi... - SAP Community)。
可运行示例
Main.controller.js(标准逻辑)
代码语言:javascript代码运行次数:0运行复制sap.ui.define([
'sap/ui/core/mvc/Controller'
], function (Controller) {
'use strict';
return Controller.extend('demo.std.controller.Main', {
onInit: function () {
// 标准初始化
this.byId('msg').setText('Standard init done');
// 钩子:留给扩展方追加逻辑
if (this.extHookAfterInit) {
this.extHookAfterInit();
}
}
});
});
Ext.controller.js(扩展逻辑)
代码语言:javascript代码运行次数:0运行复制sap.ui.define([], function () {
'use strict';
return {
extHookAfterInit: function () {
// 二次开发者逻辑
this.byId('msg').setText('Extension logic executed');
}
};
});
manifest.json(声明合并)
代码语言:json复制{
'sap.ui5': {
'controllerExtensions': {
'demo.std.controller.Main': {
'controllerName': 'demo.ext.controller.Ext'
}
}
}
}
启动应用即可看到文本由扩展逻辑覆盖。整个过程零侵入,恰好对应 Radar 对“插件化架构”在大型前端中的 Adopt 建议 (UI5 extension of controller and lifecycle methods - Stack Overflow)。
注意事项
- 对 onInit / onExit 等生命周期方法,扩展文件若覆写同名函数,框架会自动串联执行,顺序与文档描述一致 (Controller Extension - SAP Help Portal)。
- 对自定义 hook 必须先在标准 controller 暴露空实现,否则框架无法识别 (How to invoke hook extension in controller extensi... - SAP Community)。
- 若同一 controller 有多级扩展,应避免循环依赖;官方不建议多级链式 controller 继承,可改用 BaseController + mixin 组合模式 (Issue extending a controller that extends another controller)。
manifest 中的 customizing 速查表
节点 | 目标 | 示例 | 参考文档 |
---|---|---|---|
| XML / JS 视图插槽 |
| (View Extension) |
| 控制器逻辑钩子 |
| (Using Controller Extension - Documentation - Demo Kit - SAPUI5 SDK) |
| 细粒度重排视图属性 | 隐藏字段 | (Developer Adaptation - SAPUI5 Flexibility - SAP Help Portal) |
与 SAPUI5 flexibility 协同
UI5 Flexibility 让业务用户在 Runtime 自行拖拽修改,可保存到 Layer 中并随版本迁移 (SAPUI5 Flexibility: Adapting UIs Made Easy - SAP Help Portal, SAPUI5 Flexibility Services: Adapting UIs Made Easy)。然而 Flex 修改主要面向最终用户,而 extension point / hook 则面向开发阶段,用于注入整块功能代码。二者可以并存:先在视图预埋 ExtensionPoint,再允许业务用户对注入的 Fragment 做 Flex 调整,兼顾开发到运营全链路的可演进性 (Developer Adaptation - SAPUI5 Flexibility - SAP Help Portal)。
流程建议
- 规划阶段:在每个业务模块草图里标记潜在变动区,转化为 ExtensionPoint / hook;同时把默认实现与预期替换者写入 ADR(Architecture Decision Record)。
- 编码阶段:以单元测试验证占位符存在;利用 UI5 QUnit 模拟 manifest 注入,确保默认内容与替换内容均可渲染。
- 持续集成:在 pipeline 中引入 open-ui5-verifier,检测视图文件里 ExtensionPoint 是否被错误删除,防止回归。
- 运维阶段:结合 UI5 Flexibility Layer 做小范围 A/B Test,再根据数据逐步推广自定义插件。
省流版
通过在 XML 视图安放 extension point
、在控制器公开 extension hook
,SAP UI5 应用自然具备了 ThoughtWorks 技术雷达倡导的“可演进”特质:核心代码稳定,外围能力灵活替换。只要在项目初期养成“先留钩子再写功能”的习惯,就能让未来的业务需求像拼积木一样按需组合,而不必在升级窗口里痛苦比对冲突。希望本文示例能帮助大家在下一个 UI5 项目里迈出 Extension First
的第一步。