Vue 3.6 的第 13 个候选版:八条修复里有七条在 Vapor 编译器里

3.6.0-rc.13 的更新清单几乎全指向 compiler-vapor,另外还有一条把异步组件的 200ms 默认延迟改回去了。这两件事说明 Vapor 还在补语义等价,而默认值也是 API。

Vue 3.6.0-rc.13 是 10 月 23 日发的。13 个候选版这件事本身就说明 3.6 的分量:一个只加 API 的小版本不会走到 rc.13。打开更新清单,八条修复里七条是 compiler-vapor 的,第八条在 runtime-core。

清单的形状

先把修的东西列出来,按类别看比按顺序看清楚:

类别 修的问题
指令语义 v-once 在 v-else-if 链上与 vdom 的行为对齐
编译产物 长 v-else-if 链编译成扁平的 createIfChain
表达式 对象简写别名带默认值或计算 key 时补上 key
HTML 解析 foreign content 里的 breakout 元素、table 里的文本改为命令式创建
解析边界 解析器会提前闭合祖先时,给后代一个自己的模板
结束标签 父元素无法闭合时,保留特殊元素的结束标签
内置组件 非根位置的 Transition / KeepAlive 子节点标记为单根

rc.13 的发布页

这张表里没有一条是「新功能」。全是语义等价问题:Vapor 编译器的目标是把模板编译成直接操作 DOM 的代码,不再经过虚拟 DOM。可是「不经过 vdom」不等于「行为可以不一样」—— v-once 什么时候跳过更新、v-else-if 链在第几个分支命中、Transition 的子节点算不算单根,这些在 vdom 里有既成事实,Vapor 必须一条一条对齐。

长 v-else-if 链为什么要扁平化

createIfChain 这条值得单独说。假设模板里有一串 v-if / v-else-if / v-else-if ... / v-else,朴素编译是嵌套的:每个 v-else-if 都嵌在前一个的 else 分支里。十个分支就是十层嵌套,运行时要一层层进去判断。

扁平化之后是一张判断表:按顺序求条件,命中哪个就渲染哪个。分支多的时候差别很明显 —— 嵌套是 O(n) 的深度,扁平是 O(n) 的顺序,但每次更新的路径长度不同:嵌套版本里改变第一个条件,整棵嵌套结构都要重新走。

// 大意:编译期把链拍平成一个分支数组,运行时顺序求值
const branches = [
  () => ok.value,      // v-if
  () => pending.value, // v-else-if
  () => true,          // v-else
];

这类改动不会让基准测试数字变好看,它解决的是「深链模板在 Vapor 下生成的代码体积与更新代价」。一个真实的后台页面里,一个表格单元格带五六个状态分支很常见。

minor 分支的 CHANGELOG,rc.13 的条目排在最上面

那第八条:异步组件的 200ms

唯一一条不在编译器里的修复是:恢复异步加载组件默认的 200ms 延迟。

defineAsyncComponent 在加载期间默认会等 200ms 再显示 loading 状态 —— 目的是避免「加载其实只用 30ms,但用户看到 loading 闪一下」的抖动。这条默认值在 3.6 的开发周期里被改掉过,rc.13 又改回来了。

我把它看成一次提醒:默认值也是 API。任何「顺手调一下默认参数」的改动,都会在真实项目里变成可见的视觉回归 —— loading 闪一下、骨架屏抖一下,用户说不出哪里不对,但会觉得这站有点糙。

对应的 PR:把默认延迟改回 200ms

现在该不该跟

我的判断是:业务项目先别升 3.6,等正式版。理由不是 Vapor 不稳,而是 rc.13 这个编号说明改动还在每天进来 —— 一天之内能看到七条编译器语义修复,意味着还有没对齐的边角。Vapor 是可选模式,但你装上的 vue 包是一整份,编译器也在里面。

值得现在就开始读的是 Vapor 的编译产物。它跟 vdom 版本的差别不是「快一点」,而是「更新路径完全不同」:vdom 是 diff 两棵树,Vapor 是编译期就知道哪个 DOM 节点对应哪个响应式依赖,更新时直接改那一个节点。理解这一点,才能判断自己的组件库里哪些写法在 Vapor 下会变贵 —— 比如大量动态组件、递归组件、运行时拼模板。

Vue 仓库里 Vapor 相关的包与文档入口

3.6 正式版大概率不会等太久。真正需要提前准备的不是升级动作,是把「模板里有没有深链条件、有没有运行时拼接」这件事先摸一遍。

← 返回文章列表

评论

…