探索 TinyVue 组件库系列之组件系统2:Vue 2 到 Vue 3 兼容层
你好,我是 Kagol,个人公众号:前端开源星球。
TinyVue 实现了一个精密的兼容层,使同一套组件代码能够无缝运行在 Vue 2、Vue 2.7 和 Vue 3 上。这一架构成就是通过统一的适配器系统实现的,该系统规范了 API 差异、提供一致的生命周期钩子,并抽象了版本特定的实现细节。兼容层确保开发者能够维护单一代码库,同时支持多个 Vue 版本,而无需在组件实现中使用条件逻辑。
适配器架构
兼容层围绕位于 packages/vue-common/src/adapter/ 的集中式适配器系统构建。该系统提供三种不同的实现,分别针对 Vue 2、Vue 2.7(包含原生 Composition API 支持)和 Vue 3。每个适配器导出相同的 API 表面,允许组件通过虚拟模块解析保持版本无关性。
该架构采用构建时解析策略,TypeScript 配置根据目标 Vue 版本确定导入哪个适配器实现。这是通过 tsconfig.json 中的路径映射实现的,将虚拟模块 virtual:common/adapter/vue 解析到相应的目录:
// Vue 2 配置
"virtual:common/adapter/vue": ["packages/vue-common/src/adapter/vue2/index.ts"]
// Vue 3 配置
"virtual:common/adapter/vue": ["packages/vue-common/src/adapter/vue3/index.ts"]
API 规范化
适配器层规范了 Vue 版本之间的基本 API 差异,为组件开发者提供统一接口。关键的规范化 API 包括:
组件定义:两个适配器都导出 defineComponent,它包装了相应版本的实现,确保跨版本一致的组件定义语法。
异步组件加载:defineAsyncComponent API 在每个版本中实现方式不同,但提供相同的功能接口。Vue 2 返回带有加载状态的渲染函数,而 Vue 3 使用原生实现,支持加载组件组合等高级功能。
VNode 创建:h() 函数从每个版本的 composition API 中暴露,Vue 3 适配器提供增强的 prop 解析能力,以处理 Vue 2 风格的事件和属性绑定。
Teleport 实现:Vue 2 接收使用 MutationObserver 的自定义 Teleport 实现来实现 portal 功能,而 Vue 3 使用原生的 Teleport 组件。
组件实例抽象
兼容层的一个关键方面是抽象组件实例差异。适配器创建统一的虚拟机(VM)对象,无论底层 Vue 版本如何,都能提供一致的组件属性访问:
export const tools = (context, mode) => {
const instance = hooks.getCurrentInstance()?.proxy
const root = instance?.$root // Vue 2
|| instance?.appContext.config.globalProperties // Vue 3
const { route, router } = useRouter(instance)
const i18n = root?.$i18n
const { dispatch, broadcast } = emitEvent(instance)
const parentHandler = parent(instance)
const childrenHandler = children(instance)
const vm = createVm({}, instance, context)
const emit = context.emit
return { root, route, router, i18n, dispatch, broadcast,
parent: parentHandler, children: childrenHandler,
vm, emit, instance }
}
createVm() 函数构建具有一致属性的代理对象,如 $attrs、$listeners、$refs 和 $emit,抽象了 Vue 2 的 options API 和 Vue 3 的 Composition API 实例结构之间的差异。
生命周期和事件系统
兼容层规范了 Vue 版本之间的生命周期钩子名称和事件通信模式。directive() 工具函数自动映射指令生命周期钩子:
Vue 2 到 Vue 3 映射:
bind→beforeMountinserted→mountedupdate→updatedunbind→unmounted
Vue 3 到 Vue 2 映射:
beforeMount→bindmounted→insertedupdated→updateunmounted→unbind
事件系统提供 $emit、dispatch(向上事件传播)和 broadcast(向下事件传播)功能,这些功能在版本间保持一致工作,抽象了 Vue 3 对 $children 和 $on/$off API 的移除。
本地化系统适配
位于 packages/vue-locale/src/ 的国际化系统提供版本特定的适配器来处理本地化。Vue 2 和 Vue 3 实现都导出相同的 API(t()、use()、i18n()、initI18n()),但使用不同的内部机制:
- Vue 2:利用原型链检测外部 VueI18n 实例,并通过原型方法提供回退翻译解析。
- Vue 3:使用 Composition API 模式,通过
createI18n工厂函数与 Vue I18n 9.x 集成,支持 legacy 和 Composition API 模式。
两个适配器都支持中文(zh-CN)、英文(en-US)、西班牙语拉丁美洲(es-LA)和葡萄牙语巴西(pt-BR)语言。
组件集成模式
组件通过使用 @opentiny/vue-common 的标准化模式与兼容层集成。每个组件从适配器导入工具并一致地使用它们:
import { $props, $prefix, $setup, defineComponent } from '@opentiny/vue-common'
import { type PropType } from '@opentiny/vue-common'
import template from 'virtual-template?pc|mobile-first'
export default defineComponent({
name: $prefix + 'Button',
props: buttonProps,
setup(props, context) {
return $setup({ props, context, template })
}
})
$setup() 函数抽象了 Vue 版本之间的设置逻辑差异,处理模板解析、属性绑定和上下文管理。组件通过虚拟模块导入指定其模板,这些导入解析为平台特定的实现。
版本检测和条件逻辑
适配器导出版本标志,组件可以在绝对需要版本特定行为时使用:
// Vue 2 适配器
export const isVue2 = true
export const isVue3 = false
// Vue 3 适配器
export const isVue2 = false
export const isVue3 = true
虽然兼容层旨在消除组件中的版本特定代码,但这些标志可用于无法抽象差异的边缘情况。Vue 2 适配器还包括对 Vue 2.7 原生 Composition API 支持的检测,以在可用时优先使用内置钩子。
适配器模式使 TinyVue 能够通过在统一 API 表面后实现版本特定实现,从单一代码库支持 Vue 2.x、Vue 2.7 和 Vue 3.x。这种方法允许组件保持版本无关性,同时在内部利用每个 Vue 版本的最优 API。
工具函数和辅助方法
兼容层包含几个工具函数,用于规范化跨版本的常见操作:
renderComponent():提供统一的方式动态渲染带有属性和上下文的组件,处理 Vue 2 和 Vue 3 渲染函数签名的差异。getCustomProps():以版本无关的方式检索组件属性,在 Vue 2 中访问$options.propsData,在 Vue 3 中访问vnode.props。getComponentName():通过不同的内部结构解析组件名称,在 Vue 2 中检查componentOptions.Ctor.extendOptions.name,在 Vue 3 中检查type.name。rootConfig()和appProperties():通过版本适当的 API 访问全局应用程序配置和原型属性。
这些工具确保在 Vue 和 React 实现之间共享的无渲染逻辑能够在 Vue 版本间一致运行。
构建和运行时集成
兼容层通过 Vite 插件和 TypeScript 配置与构建系统集成。虚拟模块系统在构建时解析导入,确保根据目标 Vue 版本打包正确的适配器代码。这种方法允许:
- 版本检测的零运行时开销(解析在构建时发生)
- 每个 Vue 版本未使用的适配器代码的 tree-shaking
- 通过统一的类型导出跨所有支持版本的类型安全
tsconfig.vue2.json 和 tsconfig.vue3.json 中的构建配置包含版本特定的 TypeScript 选项,将 Vue 编译器目标设置为匹配预期的运行时环境。
组件通过适配器模式保持单一代码库,同时针对多个 Vue 版本。虚拟模块解析系统通过仅包含目标 Vue 版本的适配器代码确保最佳包大小,消除了运行时版本检测开销的需要。
高级功能和边缘情况
兼容层处理几个需要仔细抽象的高级场景:
VNode 解析:parseVnode() 函数规范化虚拟节点结构,将 Vue 2 的 componentOptions 和 propsData 转换为与 Vue 3 的 vnode 结构兼容的统一格式。
空 VNode 检测:不同的实现适当地检查空或注释节点,考虑 Vue 2 的文本/注释节点结构与 Vue 3 的 Text 和 Comment 符号类型。
Ref 和模板引用处理:onBeforeMount() 生命周期钩子为每个版本以不同方式捕获 $refs,在 Vue 2 中迭代实例引用,在 Vue 3 中迭代引用对象。
路由器集成:useRouter() 工具抽象 Vue Router 访问,在 Vue 2 中通过 $route/$router 提供路由和路由器对象,在 Vue 3 中通过全局属性提供。
这些高级功能确保复杂的组件行为在 Vue 版本间一致工作,而无需组件级条件逻辑。
联系我们
GitHub:https://github.com/opentiny/tiny-vue(欢迎 Star ⭐)
官网:https://opentiny.design/tiny-vue
个人博客:kagol.com.cn
小助手微信:opentiny-official
公众号:OpenTiny
更多推荐


所有评论(0)