前端

Odoo 字段为什么不是“widget 名字对上就行”:field registry、formatter、parser 与解析优先级讲透

很多人做字段二开时,关注点只停留在 widget 名称和组件模板,但 Odoo 真正复杂的是解析优先级:同一个字段会经过 registry 选型、supportedTypes 校验、extractProps、formatter/parser 乃至 view/jsClass 前缀回退。本文专门拆开这条“字段到底如何被解析成一个可运行 widget”的链路。

前端
进阶 开发者 1 分钟阅读
0 评论 0 点赞 0 收藏 5 阅读

很多人第一次做 Odoo 字段二开,会把问题想得很简单:

  • XML 上写个 widget="xxx"
  • registry 里注册一个同名字段;
  • 页面能渲染出来就算完成。

这只能算摸到最外层。

真正决定字段组件怎么运行的,不只是 widget 名字,而是一整条解析链:

  1. field.js 先决定找谁
  2. registry 再决定这个候选项是否支持当前字段类型
  3. extractProps 决定节点配置怎样转成组件 props
  4. formatter / parser 决定显示值与输入值如何往返
  5. viewTypejsClass 还会改变整个选型优先级。

所以 Odoo 字段体系真正值得研究的,不只是 widget 本身,而是widget 是怎样被解析出来的

一、getFieldFromRegistry() 先解决的不是组件渲染,而是查找优先级

field.js 里的 getFieldFromRegistry(fieldType, widget, viewType, jsClass) 很关键。

它不是简单按 widget 名字取值,而是先构造一组前缀:

  • 如果有 jsClass,优先级是 jsClassviewType → 空前缀;
  • 否则就是 viewType → 空前缀。

这意味着同一个 widget key 并不是全局唯一语义,它可能在不同上下文下解析成不同实现,比如:

  • list.xxx
  • form.xxx
  • 某个自定义 jsClass.xxx

这套规则的意义在于:

字段实现不是孤立组件,而是依附于“当前视图语境”的可替换实现。

很多定制项目一上来就往全局 fields registry 塞组件,最后影响面越来越大,问题就出在忽略了这个语境优先级。

二、widget 指定了也不代表一定合规,supportedTypes 仍然会兜底校验

getFieldFromRegistry() 找到 widget 后,并不会无条件信任它。

如果该 widget 声明了 supportedTypes,而当前字段类型不在其中,Odoo 会显式警告。

这说明 registry 项并不是“名字对上就一定能跑”的黑盒,而是带有类型边界的能力声明。

例如 char_field.js 就明确声明:

  • supportedTypes: ["char", "text"]

这类声明有两个作用:

  1. 防止错配被静默吞掉;
  2. 让同名 widget 可以在不同字段类型之间保留合理边界。

换句话说,字段 registry 不是随意映射表,而更像一张带类型约束的解析表

三、extractProps 才是“XML 节点语言”和“组件语言”之间的翻译器

很多初学者只看组件 props,却忽略 props 是怎么来的。

实际上,registry 项里的 extractProps 才是最关键的中间层。

char_field.js 为例,extractProps 会从:

  • attrs
  • options
  • placeholder

里抽出真正组件关心的东西,例如:

  • isPassword
  • dynamicPlaceholder
  • autocomplete

这说明 field widget 并不是直接吃 XML 原始节点,而是先经过一次领域化翻译

没有这层翻译,就会出现两种坏味道:

  • 组件自己到处解析 attrs/options;
  • 或 XML 细节直接泄漏进组件内部。

Odoo 明显更偏好 registry 项承担翻译责任,组件只消费已经整理好的 props。

四、formatter 和 parser 让字段解析不止停在“选中哪个组件”

字段选型只是第一步。真正进入可编辑状态后,还要解决一件更细的事:

页面上显示的值,和用户输入回模型的值,本来就不一定一样。

formatters.jsparsers.js 正是在承认这个现实。

比如:

  • formatChar 可以处理 password 显示;
  • formatDate / formatDateTime 要处理 locale 和 numeric 模式;
  • parseFloat / parseInteger 要兼容本地化分隔符;
  • parsePercentageparseMonetary 还要处理特殊输入语义。

尤其 parsers.js 很值得注意,它并不是傻乎乎地 Number(value)

  • 会考虑 localization.thousandsSepdecimalPoint
  • 会兼容带 = 的数学表达式;
  • 某些场景还能返回 Operation,表示增量更新意图而非最终纯值。

这说明 parser 本质上不是“字符串转类型”,而是在做面向业务输入语义的解释

五、字段解析链真正的顺序是什么

把这些源码串起来,更准确的顺序应该是:

  1. 视图解析阶段,Field.parseFieldNode() 读取节点与字段元信息;
  2. getFieldFromRegistry()jsClass / viewType / 默认前缀查候选实现;
  3. 候选项通过 supportedTypes 等约束完成适配校验;
  4. extractProps 把 XML 配置转成组件 props;
  5. 渲染阶段组件消费 record 与 props;
  6. 显示与输入再分别通过 formatter / parser 参与值流。

这样你就会明白,字段 widget 从来不是“名字对上就结束”,而是被逐层解析成一个受约束的运行单元

六、为什么这个角度对二开很重要

因为很多字段二开的问题,根本不在组件模板,而在解析链某一层选错了位置:

  • 本该做 viewType 定制,却硬改全局 widget;
  • 本该做 extractProps 翻译,却把 XML 解析逻辑塞进组件;
  • 本该用 formatter/parser 处理值语义,却直接在模板里字符串拼接。

最终结果往往是:

  • 某个场景能跑;
  • 换个视图或语言环境就坏;
  • 代码复用性越来越差。

结语

Odoo 字段体系最值得学习的,不只是 widget 组件写法,而是它把“字段解析”拆成了几层稳定边界:

  • registry 负责选型;
  • supportedTypes 负责边界;
  • extractProps 负责翻译;
  • formatter / parser 负责值语义。

所以当你再看到一个 <field widget="xxx" /> 时,更准确的理解应该是:

这不是在“指定一个组件名”,而是在启动一整条字段解析与值语义协作链路。

DISCUSSION

评论区

想参与讨论?先 登录 再发表评论。
还没有评论,你可以成为第一个留言的人。