我放弃了 Vue/React,选择自研框架

一、框架选型背景分析

1.1 前端生态的现状

当前前端开发领域呈现出"框架为王"的格局。Vue 和 React 作为两大主流框架,占据了市场的绝大部分份额,围绕它们形成了庞大的生态系统:状态管理、路由、UI 组件库、构建工具、测试框架... 每一层都有数十个选择。

这种繁荣的背后,是开发者需要持续学习和维护的认知负担。根据 npm 统计,前端领域每周发布的新包超过 10000 个,而框架及其周边生态的更新频率更是令人目不暇接。

1.2 商业项目中的框架依赖困境

在企业级开发中,框架选择往往意味着长期的技术绑定:

  • 升级成本高:从 Vue 2 迁移到 Vue 3,或从 React 17 升级到 React 18+,都需要大量的重构工作
  • 生态锁定:一旦选择某个框架,配套的状态管理、路由、UI 库等都倾向于选择该生态下的方案
  • 定制能力受限:当业务需求超出框架提供的能力范围时,往往需要进行大量的 hack 或等待官方支持

1.3 决策的触发点

促使我做出自研框架决定的直接原因,是一个实际项目中的技术挑战:我们需要实现一个复杂的可视化编辑器,要求极高的渲染性能和灵活的自定义渲染能力。在尝试了 Vue、React 和 Solid 之后,发现没有一个框架能够同时满足以下需求:

  1. 高频状态更新下的流畅渲染
  2. 可替换的渲染后端(DOM/Canvas/WebGL)
  3. 对渲染过程的细粒度控制

二、主流框架使用痛点阐述

2.1 Vue 的局限性

Vue 3 以其优雅的响应式系统和渐进式设计著称,但在实际使用中仍存在一些痛点:

黑盒内置组件TeleportTransition 等组件虽然功能强大,但实现细节对开发者完全不透明。当需要定制动画逻辑或扩展传送门功能时,只能通过 hack 方式实现。

2.2 React 的挑战

React 以其灵活的 JSX 语法和庞大的生态系统闻名,但同样存在问题:

Hooks 规则的复杂性useEffectuseMemouseCallback 的依赖数组管理是一个永恒的痛点,容易引发难以排查的 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 编译。该插件的核心工作流程是:

  1. 保留 JSX 不转译 :配置 esbuild/oxc 的 jsx: 'preserve',让构建工具不处理 JSX
  2. 自定义转换管道 :在 transform 钩子中实现 JSX → createView 的转换逻辑
  3. 编译宏支持 :内置对 v-ifv-elsev-modelv-show 等指令的处理
  4. 宏组件支持 :内置对 SwitchIfBlock 等组件的编译优化
  5. 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 挑战四:服务端渲染的实现

问题:如何实现服务端渲染和客户端水合?

解决方案 :实现 renderToStringrenderToStream 两个核心 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 自研框架的价值

自研框架带给我的不仅仅是一个可用的工具,更重要的是:

  1. 深度理解:对前端框架的底层原理有了前所未有的理解
  2. 技术掌控:不再被框架的 API 变动绑架,能够自主掌控技术路线
  3. 创新能力:积累了从 0 到 1 构建复杂系统的经验

7.2 适合自研框架的场景

自研框架并非适合所有项目。以下场景更适合考虑自研:

  • 对性能有极致要求的项目
  • 需要深度定制框架行为的项目
  • 追求架构透明度的团队
  • 有足够技术能力和时间投入的团队

7.3 给其他开发者的建议

如果你也在考虑自研框架,我的建议是:

  1. 明确目标:清楚自己想要解决什么问题,不要为了造轮子而造轮子
  2. 从简单开始:先实现最小可用版本,再逐步完善
  3. 保持开放:参考优秀框架的设计,但要有自己的思考
  4. 持续迭代:框架是活的,需要不断优化和改进

写在最后:Vitarx 是我两年技术探索的结晶,它可能不是最完美的框架,但它是一个"看得见、摸得着"的框架。如果你也对前端框架的底层原理感兴趣,欢迎加入我们的交流群一起探讨。

相关推荐
Asize2 小时前
HTML5 Canvas 基础:从按帧动画到 ECharts 数据可视化
前端·javascript·canvas
默_笙2 小时前
🎄 后端给我一堆扁平数据,我 10 行代码把它变成了树
前端·javascript
Mahut2 小时前
我用 Electron + FFmpeg 做了一个本地视频处理工作站 ClipForge
前端·ffmpeg·electron
前端Hardy2 小时前
又一个 AI 神器火了!
前端·javascript·后端
锋行天下2 小时前
我试图优化 Vite 的拆包,结果首屏慢了 10 倍
前端·vue.js·架构
PBitW2 小时前
GPT训练我的第二天,我表示不过如此!!!😕😕😕
前端·javascript·面试
用户99045017780092 小时前
学习了AI修图,我把自己闲鱼出租房照片整成airbnb风格了
前端
kyriewen3 小时前
白宫直接给 OpenAI 下了限制令,GPT-5.6 不能随便放出来了
前端·javascript·面试
PedroQue994 小时前
Vite插件v0.2.6:架构优化与自动化升级
前端·vite