关于 fix-control-renderer
fix-control-renderer 是一项开发技能,用于处理 UI5 linter 报告的控件渲染器问题。它指导修改渲染器声明、API 版本、模块导入、生命周期钩子和静态属性等需要理解渲染行为的代码。
主要功能
- 01修复缺失的渲染器声明
- 02将字符串渲染器声明改为导入
- 03将渲染器更新为 apiVersion 2
- 04添加所需的 IconPool 导入
- 05将 rerender 逻辑迁至生命周期钩子
- 06将渲染器属性设为静态
适用场景
- 01处理 UI5 linter 的渲染器问题
- 02升级依赖渲染器自动发现的控件
- 03重新运行 linter 验证渲染变更
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ ui5/ plugins-coding-agents --skill fix-control-renderer安装后在 Claude Code 中输入/fix-control-renderer确认已加载。
附带文件references/renderer-api-mapping.md
详细介绍
修复控件渲染器问题
此技能用于修复 UI5 linter 检测到但无法自动修复的控件渲染器问题,因为修复这些问题需要理解控件的渲染行为和模块依赖关系。
处理的 Linter 规则
| 规则 ID | 消息模式 | 此技能的处理方式 |
|---|---|---|
no-deprecated-control-renderer-declaration | 控件“...”缺少渲染器声明 | 添加 renderer: null 或导入渲染器 |
no-deprecated-control-renderer-declaration | 控件“...”的渲染器“...”声明已弃用 | 导入渲染器模块并直接赋值 |
no-deprecated-api | 检测到使用已弃用的渲染器。请显式定义 {apiVersion: 2} | 在渲染器对象中添加 apiVersion: 2 |
no-deprecated-api | 使用 RenderManager 的 icon() 方法时,必须导入 "sap/ui/core/IconPool" 模块 | 添加 IconPool 导入 |
no-deprecated-api | 控件“...”覆盖了已弃用的方法 'rerender' | 移除覆盖,并将代码移至生命周期钩子 |
ui5-class-declaration | 控件渲染器必须是静态属性 | 将 renderer 属性设为静态 |
何时使用
当你看到如下 linter 输出时,请应用此技能:
MyControl.js:5:1 error Control 'my.app.control.MyControl' is missing a renderer declaration no-deprecated-control-renderer-declaration
MyControl.js:10:5 error Deprecated declaration of renderer 'my.app.control.MyControlRenderer' for control 'my.app.control.MyControl' no-deprecated-control-renderer-declaration
MyControl.js:15:5 error Use of deprecated renderer detected. Define explicitly the {apiVersion: 2} parameter no-deprecated-api
MyControl.js:20:5 error "sap/ui/core/IconPool" module must be imported when using RenderManager's icon() method no-deprecated-api
MyControl.js:25:5 warning The control renderer of 'MyControl' must be a static property ui5-class-declaration
背景:为什么使用 apiVersion: 2?
UI5 的渲染框架从即时 DOM 操作模型(apiVersion 1)演进为语义化渲染模型(apiVersion 2 和 4)。主要区别如下:
- apiVersion 1(已弃用):通过
oRm.write()、oRm.writeAttribute()等直接操作 DOM。 - apiVersion 2:使用
oRm.openStart()、oRm.openEnd()、oRm.text()、oRm.close()等语义化方法。 - apiVersion 4:与 2 相同,并针对现代 UI5 进行了额外的性能优化。
如果未显式指定 apiVersion,UI5 会假定使用旧式渲染,这会导致同步加载和性能问题。
隐式渲染器自动发现(已在现代 UI5 中移除)
在 UI5 1.x 中,如果控件未声明 renderer 属性,框架会自动在控件的模块路径后附加 Renderer,尝试加载渲染器模块。例如,对于 sap/m/Button,框架会检查 sap/m/ButtonRenderer 是否存在;如果存在,就会加载该模块并将其用作渲染器。
此隐式自动发现机制已在现代 UI5 中移除。每个控件都必须显式声明其渲染器。如果控件依赖自动发现且没有 renderer 属性,就会在运行时出错。Linter 会将此问题标记为 no-deprecated-control-renderer-declaration,并显示消息“控件‘...’缺少渲染器声明”。
现代化工作流如下:检查默认路径下是否存在 <ControlName>Renderer 模块 → 如果存在,通过 sap.ui.define 导入该模块 → 将其赋值给 renderer 属性。
修复策略
1. 缺少 Renderer 声明
问题:Control 类完全没有声明 renderer。
// Before - triggers no-deprecated-control-renderer-declaration
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: {
properties: { ... }
}
// No renderer declaration!
});
});
修复策略 A——不需要渲染(control 是抽象的,或使用子 control):
// After - explicitly declare no renderer
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: {
properties: { ... }
},
renderer: null
});
});
修复策略 B——Renderer 位于单独的文件中(包括隐式自动发现):
在 UI5 1.x 中,许多 control 依赖框架的隐式自动发现机制——它们没有 renderer 属性,但默认路径下存在 <ControlName>Renderer.js 文件,该文件会自动加载。由于现代 UI5 已移除此自动发现机制,因此需要显式导入。
查找 renderer 的方法:
- 推导预期的 renderer 路径:在 control 的模块路径后追加
Renderer。对于my/app/control/MyControl,检查是否存在my/app/control/MyControlRenderer。 - 在项目中查找该文件(例如,与 control 位于同一目录下的
MyControlRenderer.js)。 - 如果 renderer 文件存在,则导入并赋值。如果不存在,则使用修复策略 A(
renderer: null)或修复策略 C(内联 renderer)。
// After - import and assign the renderer module
sap.ui.define([
"sap/ui/core/Control",
"./MyControlRenderer"
], function(Control, MyControlRenderer) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: {
properties: { ... }
},
renderer: MyControlRenderer
});
});
重要:导入 renderer 后,还要检查 renderer 模块本身是否包含 apiVersion: 2。如果没有,这属于单独的 lint 检查问题——请参阅下文第 3 节“Renderer 中缺少 apiVersion”。
修复策略 C——添加内联 renderer:
// After - define renderer inline with apiVersion: 2
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: {
properties: { ... }
},
renderer: {
apiVersion: 2,
render: function(oRm, oControl) {
oRm.openStart("div", oControl);
oRm.class("myControl");
oRm.openEnd();
// Render content here
oRm.close("div");
}
}
});
});
2. 基于字符串的 Renderer 声明
问题:将 renderer 声明为字符串会导致同步加载。
// Before - triggers no-deprecated-control-renderer-declaration
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: { ... },
renderer: "my.app.control.MyControlRenderer" // String = sync loading!
});
});
修复策略:导入 renderer 模块并直接赋值。
// After - import renderer module
sap.ui.define([
"sap/ui/core/Control",
"my/app/control/MyControlRenderer"
], function(Control, MyControlRenderer) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: { ... },
renderer: MyControlRenderer
});
});
3. Renderer 中缺少 apiVersion
问题:Renderer 对象或函数未声明 apiVersion。
// Before - triggers no-deprecated-api
renderer: {
render: function(oRm, oControl) {
oRm.write("<div");
oRm.writeControlData(oControl);
oRm.write(">");
oRm.write("</div>");
}
}
// OR - function without apiVersion
renderer: function(oRm, oControl) {
oRm.write("<div>");
oRm.write("</div>");
}
修复策略:添加 apiVersion: 2,并转换为语义化渲染 API。
// After - with apiVersion: 2 and semantic methods
renderer: {
apiVersion: 2,
render: function(oRm, oControl) {
oRm.openStart("div", oControl);
oRm.openEnd();
oRm.close("div");
}
}
apiVersion 1 到 apiVersion 2 的方法转换:
| 旧方法(apiVersion 1) | 新方法(apiVersion 2) |
|---|---|
oRm.write("<tag") | oRm.openStart("tag") 或 oRm.voidStart("tag") |
oRm.write(">") | oRm.openEnd() 或 oRm.voidEnd() |
oRm.write("</tag>") | oRm.close("tag") |
oRm.write(text) | oRm.text(text) |
oRm.writeControlData(oCtrl) | 将 control 作为第 2 个参数传入:oRm.openStart("div", oControl) |
oRm.addClass("cls") | oRm.class("cls") |
oRm.writeAttribute("name", val) | oRm.attr("name", val) |
完整的转换表及示例,请阅读 references/renderer-api-mapping.md。
4. 缺少 IconPool 导入
问题:未导入 IconPool 就使用 oRm.icon()。
// Before - triggers no-deprecated-api
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
renderer: {
apiVersion: 2,
render: function(oRm, oControl) {
oRm.openStart("div", oControl);
oRm.openEnd();
oRm.icon("sap-icon://accept"); // IconPool not imported!
oRm.close("div");
}
}
});
});
修复策略:将 IconPool 添加到导入项中。即使代码中未直接引用它,也必须导入。
// After - IconPool imported
sap.ui.define([
"sap/ui/core/Control",
"sap/ui/core/IconPool" // Required for oRm.icon()
], function(Control, IconPool) {
"use strict";
return Control.extend("my.app.control.MyControl", {
renderer: {
apiVersion: 2,
render: function(oRm, oControl) {
oRm.openStart("div", oControl);
oRm.openEnd();
oRm.icon("sap-icon://accept");
oRm.close("div");
}
}
});
});
5. 已弃用的 rerender() 重写
问题:在 UI5 1.121+ 中,重写 rerender() 方法不再生效。
// Before - triggers no-deprecated-api
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
renderer: { ... },
rerender: function() {
// Custom logic before rerendering
this._prepareForRender();
Control.prototype.rerender.apply(this, arguments);
// Custom logic after rerendering
this._finishRender();
}
});
});
修复策略:将渲染前逻辑移至 onBeforeRendering(),将渲染后逻辑移至 onAfterRendering()。
// After - use lifecycle hooks instead
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
renderer: { ... },
onBeforeRendering: function() {
// Called before each render (initial + re-renders)
this._prepareForRender();
},
onAfterRendering: function() {
// Called after each render (initial + re-renders)
this._finishRender();
}
});
});
实施步骤
-
使用 --details 运行 linter,获取更多上下文:
npx @ui5/linter --details -
根据 linter 输出识别错误模式(规则 ID + 消息)
-
确定 control 的渲染需求:
- control 是否需要自定义渲染?
- 是否存在单独的 renderer 文件?检查默认路径:同一目录下的
<ControlName>Renderer.js(UI5 1.x 自动发现路径) - renderer 是否使用
oRm.icon()?
-
应用相应的转换:
- 对于缺少声明:检查默认路径下是否存在
<ControlName>Renderer.js(自动发现)。如果存在,则导入并赋值;如果不存在,则添加renderer: null或创建内联 renderer - 对于字符串声明:转换为模块导入
- 对于缺少 apiVersion:添加
apiVersion: 2并转换渲染方法 - 对于 IconPool:将导入项添加到 sap.ui.define 依赖项中
- 对于 rerender 重写:将逻辑移至生命周期钩子
- 对于非静态方法:添加
static关键字(ES6 类)
- 对于缺少声明:检查默认路径下是否存在
-
重新运行 linter,验证修复结果
示例修复会话
给定的 linter 输出:
npx @ui5/linter --details
MyControl.js:5:1 error Deprecated declaration of renderer 'my.app.control.MyControlRenderer' for control 'my.app.control.MyControl' no-deprecated-control-renderer-declaration
Details: Defining the control renderer by its name may lead to synchronous loading of the control renderer module.
MyControl.js:20:5 error Use of deprecated renderer detected. Define explicitly the {apiVersion: 2} parameter in the renderer object no-deprecated-api
Details: See: https://ui5.sap.com/#/topic/c9ab34570cc14ea5ab72a6d1a4a03e3f
修复前:
sap.ui.define([
"sap/ui/core/Control"
], function(Control) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: {
properties: {
text: { type: "string", defaultValue: "" }
}
},
renderer: "my.app.control.MyControlRenderer"
});
});
// MyControlRenderer.js (separate file)
sap.ui.define([], function() {
"use strict";
var MyControlRenderer = {};
MyControlRenderer.render = function(oRm, oControl) {
oRm.write("<div");
oRm.writeControlData(oControl);
oRm.addClass("myControl");
oRm.writeClasses();
oRm.write(">");
oRm.writeEscaped(oControl.getText());
oRm.write("</div>");
};
return MyControlRenderer;
});
修复后:
sap.ui.define([
"sap/ui/core/Control",
"./MyControlRenderer"
], function(Control, MyControlRenderer) {
"use strict";
return Control.extend("my.app.control.MyControl", {
metadata: {
properties: {
text: { type: "string", defaultValue: "" }
}
},
renderer: MyControlRenderer
});
});
// MyControlRenderer.js (separate file) - updated
sap.ui.define([], function() {
"use strict";
var MyControlRenderer = {
apiVersion: 2
};
MyControlRenderer.render = function(oRm, oControl) {
oRm.openStart("div", oControl);
oRm.class("myControl");
oRm.openEnd();
oRm.text(oControl.getText());
oRm.close("div");
};
return MyControlRenderer;
});
注意事项
-
继承以下基类的控件不需要声明渲染器:
sap/ui/core/mvc/Viewsap/ui/core/XMLCompositesap/ui/core/webc/WebComponentsap/uxap/BlockBase
-
apiVersion: 4也有效,并且能为现代 UI5 提供额外的优化 -
从 apiVersion 1 转换为 2 时,请确保所有
write()调用都已正确转换为语义化方法 -
即使代码中未使用
IconPool变量,也需要在模块加载时导入 IconPool,以注册图标字体
相关技能
- fix-js-globals:对于非渲染器 JavaScript 文件(例如控制器、工具程序)中的
no-globals错误,请使用 fix-js-globals — 它可以处理sap.ui.define依赖项的添加和全局访问的替换 - fix-pseudo-modules:如果渲染器代码还包含枚举或 DataType 伪模块导入,请使用 fix-pseudo-modules 处理这些特定问题
- fix-library-init:对于
Library.init()/Lib.init()apiVersion 错误(“Deprecated call to ... Use the {apiVersion: 2} parameter”),请使用 fix-library-init — 它处理的是库初始化,而不是渲染器对象