一、框架选型背景分析
1.1 前端生态的现状
当前前端开发领域呈现出"框架为王"的格局。Vue 和 React 作为两大主流框架,占据了市场的绝大部分份额,围绕它们形成了庞大的生态系统:状态管理、路由、UI 组件库、构建工具、测试框架... 每一层都有数十个选择。
这种繁荣的背后,是开发者需要持续学习和维护的认知负担。根据 npm 统计,前端领域每周发布的新包超过 10000 个,而框架及其周边生态的更新频率更是令人目不暇接。
1.2 商业项目中的框架依赖困境
在企业级开发中,框架选择往往意味着长期的技术绑定:
- 升级成本高:从 Vue 2 迁移到 Vue 3,或从 React 17 升级到 React 18+,都需要大量的重构工作
- 生态锁定:一旦选择某个框架,配套的状态管理、路由、UI 库等都倾向于选择该生态下的方案
- 定制能力受限:当业务需求超出框架提供的能力范围时,往往需要进行大量的 hack 或等待官方支持
1.3 决策的触发点
促使我做出自研框架决定的直接原因,是一个实际项目中的技术挑战:我们需要实现一个复杂的可视化编辑器,要求极高的渲染性能和灵活的自定义渲染能力。在尝试了 Vue、React 和 Solid 之后,发现没有一个框架能够同时满足以下需求:
- 高频状态更新下的流畅渲染
- 可替换的渲染后端(DOM/Canvas/WebGL)
- 对渲染过程的细粒度控制
二、主流框架使用痛点阐述
2.1 Vue 的局限性
Vue 3 以其优雅的响应式系统和渐进式设计著称,但在实际使用中仍存在一些痛点:
黑盒内置组件 :Teleport、Transition 等组件虽然功能强大,但实现细节对开发者完全不透明。当需要定制动画逻辑或扩展传送门功能时,只能通过 hack 方式实现。
2.2 React 的挑战
React 以其灵活的 JSX 语法和庞大的生态系统闻名,但同样存在问题:
Hooks 规则的复杂性 :useEffect、useMemo、useCallback 的依赖数组管理是一个永恒的痛点,容易引发难以排查的 Bug。
tsx
// React Hooks 的依赖管理陷阱
useEffect(() => {
fetchData(id).then(setData)
}, [id]) // 漏写依赖会导致闭包问题
虚拟 DOM 的性能开销:虽然 React 18 引入了并发渲染和 Fiber 架构,但在高频更新场景下,虚拟 DOM 的 diff 开销依然不可忽视。
2.3 信号框架的不足
Solid、Svelte 等信号驱动框架在性能上表现出色,但它们的编译时优化策略带来了新的问题:
编译产物不透明 :编译后的代码与源代码差异巨大,调试困难。 运行时能力受限:由于大量逻辑在编译时完成,运行时的动态性和灵活性受到限制。
三、自研框架的技术架构设计
3.1 核心设计理念
基于对主流框架的分析,我确定了自研框架 Vitarx 的三大核心设计理念:
| 设计原则 | 具体含义 | 优势 |
|---|---|---|
| 信号级精确更新 | 数据变化时仅更新关联的 DOM 节点 | 无 diff 开销,高频更新场景性能优异 |
| 运行时视图树 | 保留完整的运行时结构,支持动态操作 | 灵活的渲染编排能力 |
| 万物皆组件 | 所有内置组件用公开 API 构建 | 无黑盒,可定制,可扩展 |
3.2 分层架构
Vitarx 采用分层架构设计,各层职责清晰、依赖单向:
┌─────────────────────────────────────────────┐
│ vitarx │ ← 聚合包,统一导出
├────────────┬─────────────┬──────────────────┤
│runtime-dom │ runtime-ssr │ │ ← 平台适配层
├────────────┴─────────────┼──────────────────┤
│ runtime-core │ utils │ ← 运行时核心 & 工具
├──────────────────────────┼──────────────────┤
│ responsive │ │ ← 响应式系统
└──────────────────────────┴──────────────────┘
3.3 响应式系统设计
响应式系统是 Vitarx 的核心。与 Vue 的实现不同,Vitarx 采用双向链表依赖管理,实现了更高效的依赖追踪和触发:
typescript
/**
* DepLink --- 信号与副作用之间的双向链表节点
* 每个节点同时存在于两条链表中:
* - Signal 链表:该信号被哪些 effect 依赖
* - Effect 链表:该 effect 依赖了哪些信号
*/
class DepLink {
sigPrev?: DepLink // Signal 链表中的前驱
sigNext?: DepLink // Signal 链表中的后继
ePrev?: DepLink // Effect 链表中的前驱
eNext?: DepLink // Effect 链表中的后继
constructor(
public signal: Signal,
public effect: EffectRunner
) {}
}
Track(依赖收集):当副作用函数执行并访问响应式数据时,创建或复用链表节点,同时挂到两条链表上。
Trigger(触发更新):当数据变化时,沿 Signal 链表遍历通知所有依赖者------不需要 diff,不需要重渲染。
3.4 视图系统设计
Vitarx 的视图系统基于 View 抽象层,将 JSX 转换为轻量的视图对象,而非虚拟 DOM:
typescript
interface View {
init(ctx: ViewContext): void // 初始化
mount(container: HostContainer): void // 挂载到容器
dispose(): void // 销毁
}
这种设计使得渲染器可以完全替换------只需实现 ViewRenderer 接口:
typescript
interface ViewRenderer {
createElement(tag: string, parent: HostContainer): HostElement
createText(text: string): HostText
createComment(text: string): HostComment
append(child: HostNode, parent: HostContainer): void
remove(node: HostNode): void
setAttribute(el: HostElement, key: string, next: unknown, prev: unknown): void
// ... 其他方法
}
四、开发过程中的关键挑战与解决方案
4.1 挑战一:响应式系统的内存管理
问题:信号与 effect 之间的依赖关系如果不及时清理,会导致内存泄漏。
解决方案 :采用版本号增量标记机制。每次 effect 重新执行时,先给所有旧依赖打上过期标记,然后重新收集依赖并更新标记,最后清理过期的依赖节点。
typescript
export function trackSignal(signal: Signal): void {
const activeEffect = currentActiveEffect
if (!activeEffect) return
let link = activeEffect[DEP_INDEX_MAP]?.get(signal)
if (!link) {
link = createDepLink(activeEffect, signal)
activeEffect[DEP_INDEX_MAP]?.set(signal, link)
}
link[DEP_VERSION] = activeEffect[DEP_VERSION] // 标记为"本次仍访问"
}
4.2 挑战二:视图树的高效更新
问题:在运行时维护完整的视图树结构,如何保证更新操作的高效性?
解决方案 :引入视图切换事务(ViewSwitchTransaction) ,将视图切换操作封装为可中断、可回滚的事务。这使得 Transition 等组件可以在视图切换过程中插入动画逻辑。
typescript
onViewSwitch((tx) => {
const { prev, next } = tx
tx.stopPropagation()
// 先执行离开动画
runTransition(prev.node, 'leave', props, () => {
tx.commit() // 提交切换
// 再执行进入动画
runTransition(next.node, 'enter', props)
})
})
4.3 挑战三:JSX 运行时的自定义
问题:如何让 Vitarx 使用自己的 JSX 编译链路,而不依赖 React 的 jsx-runtime?
解决方案 :通过 @vitarx/plugin-vite 插件配合 Vite 实现自定义 JSX 编译。该插件的核心工作流程是:
- 保留 JSX 不转译 :配置 esbuild/oxc 的
jsx: 'preserve',让构建工具不处理 JSX - 自定义转换管道 :在
transform钩子中实现 JSX →createView的转换逻辑 - 编译宏支持 :内置对
v-if、v-else、v-model、v-show等指令的处理 - 宏组件支持 :内置对
Switch、IfBlock等组件的编译优化 - HMR 注入:开发模式下自动注入热更新相关代码
使用方式只需在 vite.config.ts 中引入插件:
typescript
// vite.config.ts
import vitarx from '@vitarx/plugin-vite'
export default defineConfig({
plugins: [vitarx()]
})
与 React jsx-runtime 的本质区别 :React 的 jsx-runtime 在运行时执行 createElement 调用;而 Vitarx 在构建时通过 Vite 插件将 JSX 编译为 createView 调用,运行时无需任何额外的 JSX 转换开销。
4.4 挑战四:服务端渲染的实现
问题:如何实现服务端渲染和客户端水合?
解决方案 :实现 renderToString 和 renderToStream 两个核心 API,同时在客户端实现增量水合------只更新变化的部分。
typescript
import { renderToString, renderToStream } from '@vitarx/runtime-ssr'
// 字符串渲染
const html = await renderToString(App)
// 流式渲染
const stream = await renderToStream(App)
五、性能对比测试数据
5.1 测试环境
- 测试工具:JS Framework Benchmark
- 测试环境:Chrome 120, macOS 14.0
- 测试项目:1000 行数据表格,包含创建、更新、选中、交换、删除等操作
5.2 测试结果
执行时间对比(毫秒)
| 测试场景 | Vue 3.6 | React 19 | Vitarx 4.0 |
|---|---|---|---|
| 创建1000行 | 81.9 | 82.4 | 112.2 |
| 更新1000行 | 88.2 | 84.8 | 122.4 |
| 部分更新(每10行) | 19.1 | 11.2 | 9.2 |
| 选中行高亮 | 9.8 | 5.8 | 4.5 |
| 交换两行 | 10.7 | 67.6 | 10.4 |
| 删除一行 | 20.9 | 18.1 | 17.2 |
内存分配对比(MB)
| 测试场景 | Vue 3.6 | React 19 | Vitarx 4.0 |
|---|---|---|---|
| 初始内存 | 0.86 | 1.18 | 0.83 |
| 运行内存 | 3.77 | 4.42 | 5.46 |
| 创建/清理后 | 1.23 | 1.97 | 1.16 |
传输大小对比
| 项目 | Vue 3.6 | React 19 | Vitarx 4.0 |
|---|---|---|---|
| 未压缩 | 63.7 KB | 180.3 KB | 57.8 KB |
| 压缩后 | 22.9 KB | 51.4 KB | 17.0 KB |
六、未来框架迭代规划
6.1 短期目标(2026 - 2027年)
| 目标 | 具体内容 | 优先级 |
|---|---|---|
| 生态完善 | 推出官方 UI 组件库 设计系统 | 高 |
| VitaStack | 全栈开发框架 | 高 |
| 工具链 | 开发浏览器调试插件 | 高 |
| 文档升级 | 完善官方文档和教程 | 中 |
| 状态管理 | 开发官方状态管理库 | 中 |
6.2 中期目标
| 目标 | 具体内容 | 优先级 |
|---|---|---|
| 跨平台支持 | 推出移动端和桌面端支持 | 中 |
| 性能优化 | 进一步优化首屏加载和渲染性能 | 高 |
6.3 长期目标
| 目标 | 具体内容 | 优先级 |
|---|---|---|
| AI 集成 | 探索 AI 辅助开发能力 | 低 |
| WebAssembly | 关键路径的 WASM 优化 | 低 |
| 社区建设 | 建立活跃的开发者社区 | 中 |
七、总结与反思
7.1 自研框架的价值
自研框架带给我的不仅仅是一个可用的工具,更重要的是:
- 深度理解:对前端框架的底层原理有了前所未有的理解
- 技术掌控:不再被框架的 API 变动绑架,能够自主掌控技术路线
- 创新能力:积累了从 0 到 1 构建复杂系统的经验
7.2 适合自研框架的场景
自研框架并非适合所有项目。以下场景更适合考虑自研:
- 对性能有极致要求的项目
- 需要深度定制框架行为的项目
- 追求架构透明度的团队
- 有足够技术能力和时间投入的团队
7.3 给其他开发者的建议
如果你也在考虑自研框架,我的建议是:
- 明确目标:清楚自己想要解决什么问题,不要为了造轮子而造轮子
- 从简单开始:先实现最小可用版本,再逐步完善
- 保持开放:参考优秀框架的设计,但要有自己的思考
- 持续迭代:框架是活的,需要不断优化和改进
写在最后:Vitarx 是我两年技术探索的结晶,它可能不是最完美的框架,但它是一个"看得见、摸得着"的框架。如果你也对前端框架的底层原理感兴趣,欢迎加入我们的交流群一起探讨。