前端

Odoo 视图为什么不是“后端吐一段 arch 前端直接画出来”:get_views、ArchParser 与 ViewCompiler 管线讲透

很多人学 Odoo 前端时,会把视图渲染想成“后端返回 XML arch,前端照着生成界面”。但 `view_service.js`、`view.js`、`form_arch_parser.js` 与 `view_compiler.js` 告诉我们,真正发生的是一条多段管线:视图描述先被加载,再被 parser 提炼成字段/组件元数据,最后由 compiler 转成 OWL 可运行模板。

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

很多人第一次接触 Odoo 视图,会自然地把它理解成这样:

  1. 后端把 XML arch 发给前端;
  2. 前端按 XML 结构生成表单或列表;
  3. 结束。

但如果你顺着 addons/web/static/src/views/view_service.jsview.jsform_arch_parser.jsview_compiler.js 一路看,会发现 Odoo 根本不是“直接画 XML”。

它走的是一条很典型的编译型 UI 管线:

  • 加载视图描述
  • 解析 arch,抽出字段与组件语义
  • 编译成 OWL 可运行模板
  • 再交给具体 Controller / Renderer 去跑。

这条链路很值得讲透,因为它解释了很多初学者常见困惑:

  • 为什么同一段 XML 不是“有标签就直接显示”;
  • 为什么字段节点要先过 parser;
  • 为什么 invisible、widget、button 这些东西最后会变成组件逻辑而不是原始 XML 标签;
  • 为什么视图二开要同时理解后端元数据和前端编译阶段。

一、view service 先拿到的不是“现成页面”,而是视图描述包

view_service.js 最核心的接口是:

  • loadViews(params, options)

它最终调用:

  • orm.cache({ type: "disk" }).call(resModel, "get_views", [], { ... })

注意这里后端方法叫 get_views,不是“render_view”。这已经很能说明问题。

前端拿回来的结果至少包括:

  • fields
  • relatedModels
  • views[viewType].arch
  • views[viewType].id
  • 可选的 toolbar
  • 可选的 filters
  • 可选的 custom_view_id

也就是说,前端收到的是一份视图描述数据包,而不是最终界面。

为什么这里要先把 context 过滤一遍

源码里有一段很重要:只把 lang*_view_ref 之类的键保留下来作为 filteredContext

这说明官方不想让一大堆运行时上下文直接污染 get_views 结果缓存。

因为视图结构应该尽量由:

  • 语言;
  • 指定视图引用;
  • 设备特征(如 mobile);
  • debug 状态;

这些真正影响结构的因素决定,而不是被当前业务动作里各种临时上下文带偏。

这也是为什么 view service 会在 ir.ui.view / ir.filters 更新时触发 CLEAR-CACHES:它把视图描述当成一类值得缓存、也必须在结构变更后主动失效的数据。

二、View 组件做的第一件事不是 render,而是 loadView

view.jsView 组件在 onWillStart 阶段先执行:

  • this.loadView(this.props)

这一步特别关键。

因为它说明 View 不是一个“拿到 arch 立刻画出来”的 dumb component,而是一个带加载期和运行期的容器。

它先检查:

  • resModel 是否存在;
  • type 是否有效;
  • arch/fields 是否成对给出;
  • searchViewArch/searchViewFields 是否成对给出。

这些校验都在提醒你:

一份可运行视图,不是单独一段 arch 就够了,还必须配套字段定义、搜索视图、配置与上下文。

三、ArchParser 不是“读 XML”,而是在提炼运行时语义

form_arch_parser.js 为例,它做的绝不只是遍历节点。

它会在 visitXML 过程中:

  • <field>Field.parseFieldNode(...)
  • <widget>Widget.parseWidgetNode(...)
  • 给每个字段节点生成 field_id
  • 收集 fieldNodes / widgetNodes
  • 记录 autofocusFieldIds
  • 判断 disable_autofocus
  • 计算 activeActions

这说明 parser 的目标不是“保留 XML 长相”,而是把 XML 里那些对运行时有意义的信息提前抽出来。

举个很直观的点:

同名字段在一个 form 里可以出现多次,所以 parser 不能只记字段名,必须给每个出现位置生成唯一 field_id

如果没有这一步,后面编译和渲染阶段就分不清“这个 partner_id 是页头那个,还是 notebook 页里的那个”。

parser 其实在做“从声明式结构到可执行描述”的第一轮翻译

XML arch 写的是声明式视图结构;

parser 产出的则更接近:

  • 哪些字段节点存在;
  • 它们各自的配置是什么;
  • 哪些 widget 节点存在;
  • 用户动作能力有哪些;
  • 焦点与行为元信息如何组织。

所以 parser 是视图编译链里第一层真正的“语义提纯器”。

四、ViewCompiler 才是把 arch 送进 OWL 世界的关键桥梁

view_compiler.js 更能看出 Odoo 的思路。

ViewCompiler 默认就会对这些节点做专门处理:

  • a[type] / button
  • field
  • widget

也就是说,在 Odoo 眼里:

  • button 不是普通 HTML button;
  • field 不是普通 XML tag;
  • widget 也不是最终 DOM。

它们都是需要被编译成组件或行为节点的声明式语法。

compileNode 不只是复制节点,而会处理 modifiers 与 Owl 指令

源码里能看到几件很典型的事:

  • 会去掉 t-translation
  • 会读取 modifier,比如 invisible
  • 会把 invisible 编译成 t-if
  • 会识别 t-att-*t-attf-* 等 Owl 指令
  • 会根据目标是不是组件节点,决定 attribute 如何搬运

这说明 Odoo 的视图编译,不是把 XML“原样变 HTML”,而是把声明式语法翻译成 OWL 模板表达式 + 组件 props + 条件渲染逻辑

尤其 applyInvisible() 很说明问题:

  • 它不会简单给节点加个 CSS class 隐藏;
  • 而是把表达式编译进 t-if
  • 运行时再基于 record 的 evalContextWithVirtualIds 求值。

这比“渲染完再隐藏”更靠近真正的视图语义。

五、为什么 field、button、widget 必须走编译,而不是让浏览器自己认标签

因为这些标签根本不是浏览器语义,而是 Odoo 自己定义的声明式 UI 语言。

比如 <field> 至少隐含了这些问题:

  • 用哪个字段组件;
  • 只读还是可编辑;
  • 用哪个 formatter / parser;
  • 有哪些 modifiers;
  • 与 record / model 的绑定怎么建立。

浏览器当然不懂这些。

所以 Odoo 必须先把它们翻译成前端运行时能执行的组件树。也正因为如此,视图不是“模板片段”,而更像一段 DSL(领域专用语言)。

六、加载、解析、编译分层,正是为了让视图系统可扩展

很多人会觉得这条链很长,似乎复杂过头。但如果你把 Odoo 的使用场景放进来,就知道这其实是必要复杂度。

1)后端可以主导声明式结构

业务模块通过 XML 声明视图结构,而不用自己写整套前端组件。

2)前端可以在 parser / compiler 阶段接管运行时语义

这让同一份 arch 不只是“展示结构”,还能表达:

  • 字段行为;
  • 条件显示;
  • 组件挂载;
  • 按钮动作;
  • 搜索 / toolbar / 自定义视图元数据。

3)不同 view type 可以替换各自 parser / compiler

列表、表单、kanban、calendar 都有自己的 arch parser,说明官方不是一把梭地处理所有视图,而是允许每种视图在共享总框架下定制自己的编译逻辑。

七、实战里最常见的几个误区

误区 1:把 arch 当最终界面

arch 只是输入,不是结果。真正结果是 parser 和 compiler 消化后的运行时组件结构。

误区 2:前端二开只盯 XML,不看 parser / compiler

这样最容易出现“标签写了但行为不对”“字段明明在 arch 里却没按预期工作”的困惑。

误区 3:把 invisible 当成纯 CSS 隐藏

在 Odoo 里,modifier 更像视图语义的一部分,很多时候是在编译和表达式求值层解决,不只是样式层。

误区 4:忽视 view 缓存边界

get_views 结果会缓存,结构变化也会触发清缓存。如果不了解这一点,很容易把“前端没刷新”误判成“源码没生效”。

八、结论

Odoo 视图系统的关键,不是“后端返回 XML,前端照着画”,而是:

  • get_views 返回结构化视图描述;
  • ArchParser 把声明式节点提炼成运行时语义;
  • ViewCompiler 把这些语义翻译成 OWL 模板和组件逻辑;
  • 最终由具体 View / Controller / Renderer 组合成真正可交互界面。

所以更准确的一句话是:

Odoo 视图不是 XML 驱动的静态页面,而是一条把声明式视图语言编译成前端运行时的管线。

理解了这条管线,你再去改字段 widget、按钮行为、modifiers、搜索视图或自定义 view type,思路会清楚很多:你不是在“改一段 XML”,而是在接入一条编译链。

DISCUSSION

评论区

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