第三十三章 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 class、export 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.4 本节练习
- 简答:
@odoo-module注释对 打包器 的主要作用? - 判断:任意
node_modules包可在import后直接使用,无需改__manifest__.pyassets。( )
参考答案提示:2. 错;需纳入 bundle 或 lazy 资产。
33.2 注册表(Registries)(新增)
33.2.1 知识要点(复习)
- 分类:
fields、actions、systray、main_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.4 本节练习
- 实操:在控制台列出
fields分类下 5 个 内置项名(方法以当前环境为准)。 - 简答:与 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 本节练习
- 实操:实现
my_counter,在 任意测试组件 中inc()后显示数值。 - 简答: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 本节练习
- 简答:上游升级导致 patch 失效,你如何 尽早发现?
- 判断:patch 适合作为 首选扩展点。( )
参考答案提示:1. 前端测试、升级预检、对比上游 diff。2. 错;次选。
33.5 错误处理(新增)
33.5.1 知识要点
- RPC:
orm.call等 Promise reject;try/catch+notification.add给用户 可读文案,console.error保留 技术细节(开发模式)。 - OWL:
onError(若版本提供)或 边界组件 捕获 子树异常,避免 整页白屏。 - 勿泄露:生产 不把 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.4 本节练习
- 实操:在 RPC 失败 时
notification.add区分 网络错误 与 AccessError(根据 e 类型或 message)。 - 简答:前端校验与后端校验 分工?
参考答案提示:2. UX 即时反馈 vs 最终权威;安全与规则仅在后端可靠。
本章综合练习
- 架构:说明为何避免 多个模块重复 patch 同一方法。
- 安全:举 一条 禁止仅信任前端 domain 的场景。
- 综合:画 「用户点击 → ORM → PostgreSQL」 与 「用户点击 → Controller jsonrpc」 两路径,标出 CSRF/token 差异查阅点(第三十章、第三十六章)。
- 实操:新建 只读模块,仅含 一个 Service + 一个 main_components 条目,页面显示 当前 db 名(
userservice 或等价 API)。
本章对应白皮书目录:第三十三章 JavaScript 开发。