虚拟 DOM 本身不直接支持灰度发布,但可在 patch 过程中根据灰度规则动态替换 VNode 的 type 或 props,如将 <Button> 替换为 <ButtonV2>;需在 VNode 创建阶段注入灰度标识,结合动态导入、稳定 key、服务端策略同步及曝光上报,保障体验一致性与可观测性。

虚拟 DOM 本身不直接支持灰度发布,但它为灰度逻辑提供了灵活的拦截与动态替换能力。关键不在“用 VNode 实现灰度”,而在于**在 patch 过程中根据灰度规则,对特定 VNode 的 type 或 props 做运行时决策**——比如把 <Button> 替换成 <ButtonV2>,或跳过渲染旧组件、注入实验性逻辑。
在 VNode 创建阶段注入灰度标识
不是等组件 mount 后再判断,而是在 render 函数或 JSX 编译后生成 VNode 时,就携带灰度上下文:
- 通过全局灰度配置(如
window.__GRAY_CONFIG__)或用户身份(如 uid % 100 - 在 createElement / h() 调用前,包装组件引用:例如
h(useGray(Button, ButtonV2), props) - VNode 的
type字段可设为函数、对象或异步组件,便于运行时切换,无需修改模板
在 patch 阶段劫持 VNode 比较逻辑
Vue 或 React 的 diff 算法本身不暴露钩子,但可在自定义渲染器或 patch 前置逻辑中干预:
- 重写
patch入口,在比对前检查 oldVNode 和 newVNode 的 type 是否属于灰度目标组件 - 若命中灰度且新版本已加载,将 newVNode.type 替换为新版组件定义(注意保持 key 一致,避免强制卸载)
- 对未命中的 VNode,可透传原逻辑;对灰度中组件,还可注入额外 props(如
__grayVersion: "v2")供组件内埋点或降级使用
结合异步组件 + 动态 import 实现按需加载
灰度常伴随新组件未全量打包的情况,此时 VNode 的 type 可设为 Promise:
立即学习“前端免费学习笔记(深入)”;
- 定义灰度组件工厂:
const GrayButton = () => (isInGray() ? import('./ButtonV2.vue') : import('./Button.vue')) - 在 VNode 创建时传入该工厂函数,渲染器会自动处理 loading / error 状态
- 配合 webpack 的 magic comment(如
/* webpackChunkName: "button-v2" */),确保灰度资源独立分包、按需加载
避免常见陷阱
灰度不是简单替换组件,需保障体验一致性与可观测性:
- 不要在 setup() 或 componentDidMount 中做灰度判断——此时 VNode 已固定,替换会导致状态丢失、ref 失效
- 保持 key 稳定:灰度切换时若 key 改变,会触发完整卸载重装,影响性能和状态;建议用业务 ID + 灰度标识组合生成 key
- 服务端需同步下发灰度策略:CSR 场景下,首屏 VNode 由 SSR 渲染,灰度判断必须和服务端一致,否则出现水合 mismatch
- 记录灰度曝光与转化:在 VNode 插入真实 DOM 后(如 Vue 的 mounted 钩子或 React 的 useLayoutEffect),上报组件曝光、用户行为,用于效果归因

















