mcpField
F
fix-control-renderer

fix-control-renderer

github.com/ui5开发44144 次安装1 周前更新
代码仓库

关于 fix-control-renderer

fix-control-renderer 是一项开发技能,用于处理 UI5 linter 报告的控件渲染器问题。它指导修改渲染器声明、API 版本、模块导入、生命周期钩子和静态属性等需要理解渲染行为的代码。

主要功能

  1. 01修复缺失的渲染器声明
  2. 02将字符串渲染器声明改为导入
  3. 03将渲染器更新为 apiVersion 2
  4. 04添加所需的 IconPool 导入
  5. 05将 rerender 逻辑迁至生命周期钩子
  6. 06将渲染器属性设为静态

适用场景

  1. 01处理 UI5 linter 的渲染器问题
  2. 02升级依赖渲染器自动发现的控件
  3. 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 的方法:

  1. 推导预期的 renderer 路径:在 control 的模块路径后追加 Renderer。对于 my/app/control/MyControl,检查是否存在 my/app/control/MyControlRenderer。
  2. 在项目中查找该文件(例如,与 control 位于同一目录下的 MyControlRenderer.js)。
  3. 如果 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();
        }
    });
});

实施步骤

  1. 使用 --details 运行 linter,获取更多上下文:

    npx @ui5/linter --details
    
  2. 根据 linter 输出识别错误模式(规则 ID + 消息)

  3. 确定 control 的渲染需求:

    • control 是否需要自定义渲染?
    • 是否存在单独的 renderer 文件?检查默认路径:同一目录下的 <ControlName>Renderer.js(UI5 1.x 自动发现路径)
    • renderer 是否使用 oRm.icon()?
  4. 应用相应的转换:

    • 对于缺少声明:检查默认路径下是否存在 <ControlName>Renderer.js(自动发现)。如果存在,则导入并赋值;如果不存在,则添加 renderer: null 或创建内联 renderer
    • 对于字符串声明:转换为模块导入
    • 对于缺少 apiVersion:添加 apiVersion: 2 并转换渲染方法
    • 对于 IconPool:将导入项添加到 sap.ui.define 依赖项中
    • 对于 rerender 重写:将逻辑移至生命周期钩子
    • 对于非静态方法:添加 static 关键字(ES6 类)
  5. 重新运行 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/View
    • sap/ui/core/XMLComposite
    • sap/ui/core/webc/WebComponent
    • sap/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 — 它处理的是库初始化,而不是渲染器对象

评论

支持 Markdown