第三十三章 JavaScript 开发

篇别:第八篇 前端开发

与第三十二章的关系注册表(Registries)、服务(Services)、代码补丁(Patching) 的完整语法、表格与长示例已并入 第三十二章 32.4~32.6资产与 SCSS 继承32.7。本章以 模块系统、错误处理、与 RPC 协作 为主线,并对 Registry / Service / patch复习提纲 + 延伸练习,便于单独阅读与备课。


本章学习目标

  • 使用 ES 模块/** @odoo-module */)组织前端代码,理解 与原生 ESM 的差异
  • 能口述 Registry / Service / patch适用边界,并在需要时 回到第三十二章 查 API 细节。
  • 建立 前端错误处理RPC 失败提示可观测性(Console / Network)意识。
  • 了解 与后端 Controller(jsonrpc) 的衔接点(第三十六章前置阅读)。

导读:Odoo 前端是「带约定的 ESM」

浏览器加载 web bundle 后,自定义模块通过 @odoo-module 参与 构建与依赖解析import 路径 常映射到 @web/...@odoo/owl别名;与裸 Vite/Webpack 项目不同,勿随意 引入未加入 assets 的 npm 包。本章帮助你在 官方约束安全扩展


33.1 JavaScript 模块系统

33.1.1 知识要点

  • 文件头/** @odoo-module */(注释格式以构建链为准)标记 Odoo 模块,启用 依赖分析与 lazy 策略
  • 导出export classexport function;避免 默认导出 与官方风格不一致(除非团队统一)。
  • 路径@web/core/...@web/views/...稳定公共 API;深路径 .../legacy/... 可能 下版本变动
  • 禁止:在 非模块脚本 中污染 window.odoo;多模块协作用 Registry / Service

33.1.2 案例

/** @odoo-module **/
import { registry } from "@web/core/registry";
import { MyPanel } from "./my_panel";

registry.category("main_components").add("MyLibraryPanel", { Component: MyPanel });

33.1.3 截图占位

图 33-1 构建产物或 IDE 中 @odoo-module 标记

33.1.4 本节练习

  1. 简答@odoo-module 注释对 打包器 的主要作用?
  2. 判断:任意 node_modules 包可在 import 后直接使用,无需改 __manifest__.py assets。( )

参考答案提示:2. 错;需纳入 bundlelazy 资产


33.2 注册表(Registries)(新增)

33.2.1 知识要点(复习)

  • 分类fieldsactionssystraymain_components 等;registry.category("...") 取分类。
  • 注册.add(key, value);注意 键冲突后加载模块覆盖 顺序由 depends 与清单顺序 决定。
  • 读取registry.category("fields").get("many2one") 等;调试 可在 浏览器控制台 打印(仅开发环境)。

详情与多类表格:见 第三十二章 32.4

33.2.2 案例

import { registry } from "@web/core/registry";
import { MyCharField } from "./my_char_field";

registry.category("fields").add("my_isbn_hint", { component: MyCharField });

33.2.3 截图占位

图 33-2 调试器中查看 registry.category

33.2.4 本节练习

  1. 实操:在控制台列出 fields 分类下 5 个 内置项名(方法以当前环境为准)。
  2. 简答:与 patch 相比,Registry 新字段组件 何时 更清晰

参考答案提示:2. 新增 UI 类型、无需改核心类源码时。


33.3 服务(Services)(新增)

33.3.1 知识要点(复习)

  • 定义registry.category("services").add("my_counter", myCounterService)dependencies 声明 依赖的其他服务
  • 使用useService("my_counter") 仅在 OWL 组件 setup合法上下文 调用。
  • 生命周期:随 WebClient 启动;注意 卸载清除定时器

完整计时器服务示例:见 第三十二章 32.5

33.3.2 案例(极简)

/** @odoo-module **/
import { registry } from "@web/core/registry";

export const myCounterService = {
    dependencies: [],
    start() {
        let n = 0;
        return {
            get value() {
                return n;
            },
            inc() {
                n += 1;
            },
        };
    },
};

registry.category("services").add("my_counter", myCounterService);

33.3.3 本节练习

  1. 实操:实现 my_counter,在 任意测试组件inc() 后显示数值
  2. 简答Service普通 JS 单例模块 的差异?

参考答案提示:2. 依赖注入、启动顺序、与 env 集成、可 mock


33.4 代码补丁(Patching)(新增)

33.4.1 知识要点(复习)

  • patch(SomeClass.prototype, { ... })合并原型方法super 调用 需按 官方 patching 文档 写法。
  • 风险:上游 小版本 改方法名即 失效;应 加测试启动期自检
  • 替代:能 Registry 替换组件优先 Registry,降低 耦合

对象 / 类 / OWL 三类示例:见 第三十二章 32.6

33.4.2 案例

import { patch } from "@web/core/utils/patch";
import { SomeListController } from "@web/views/list/list_controller";

patch(SomeListController.prototype, {
    setup() {
        super.setup();
        // 仅示意:例如订阅 bus
    },
});

33.4.3 本节练习

  1. 简答:上游升级导致 patch 失效,你如何 尽早发现
  2. 判断:patch 适合作为 首选扩展点。( )

参考答案提示:1. 前端测试、升级预检、对比上游 diff。2. 错;次选


33.5 错误处理(新增)

33.5.1 知识要点

  • RPCorm.callPromise rejecttry/catch + notification.add 给用户 可读文案console.error 保留 技术细节(开发模式)。
  • OWLonError(若版本提供)或 边界组件 捕获 子树异常,避免 整页白屏
  • 勿泄露:生产 不把 Python 栈 直接展示给 终端用户日志 走后端。

33.5.2 案例

import { useService } from "@web/core/utils/hooks";

// setup 内
this.notification = useService("notification");
this.orm = useService("orm");

async _load() {
    try {
        this.state.rows = await this.orm.searchRead("library.book", [], ["name"]);
    } catch (e) {
        console.error(e);
        this.notification.add("无法加载图书列表", { type: "danger" });
    }
}

33.5.3 截图占位

图 33-5 前端错误与 notification

33.5.4 本节练习

  1. 实操:在 RPC 失败notification.add 区分 网络错误AccessError(根据 e 类型或 message)。
  2. 简答:前端校验与后端校验 分工

参考答案提示:2. UX 即时反馈 vs 最终权威安全与规则仅在后端可靠


本章综合练习

  1. 架构:说明为何避免 多个模块重复 patch 同一方法
  2. 安全:举 一条 禁止仅信任前端 domain 的场景。
  3. 综合:画 「用户点击 → ORM → PostgreSQL」「用户点击 → Controller jsonrpc」 两路径,标出 CSRF/token 差异查阅点(第三十章、第三十六章)。
  4. 实操:新建 只读模块,仅含 一个 Service + 一个 main_components 条目,页面显示 当前 db 名user service 或等价 API)。

本章对应白皮书目录:第三十三章 JavaScript 开发。