一、框架選型背景分析
當前前端開發領域呈現出「框架為王」的格局。Vue 和 React 作為兩大主流框架,佔據了市場的絕大部分份額,圍繞它們形成了龐大的生態系統:狀態管理、路由、UI 元件庫、建置工具、測試框架…… 每一層都有數十個選擇。
這種繁榮的背後,是開發者需要持續學習和維護的認知負擔。根據 npm 統計,前端領域每週發布的新套件超過 10000 個,而框架及其周邊生態的更新頻率更是令人目不暇給。
在企業級開發中,框架選擇往往意味著長期的技術綁定:
促使我做出自研框架決定的直接原因,是一個實際專案中的技術挑戰:我們需要實現一個複雜的視覺化編輯器,要求極高的渲染效能和靈活的自訂渲染能力。在嘗試了 Vue、React 和 Solid 之後,發現沒有一個框架能夠同時滿足以下需求:
Vue 3 以其優雅的響應式系統和漸進式設計著稱,但在實際使用中仍存在一些痛點:
黑盒內建元件:Teleport、Transition 等元件雖然功能強大,但實作細節對開發者完全不透明。當需要客製化動畫邏輯或擴充傳送門功能時,只能透過 hack 方式實現。
React 以其靈活的 JSX 語法和龐大的生態系統聞名,但同樣存在問題:
Hooks 規則的複雜性:useEffect、useMemo、useCallback 的依賴陣列管理是一個永恆的痛點,容易引發難以排查的 Bug。
tsx 代碼解讀複製代碼// React Hooks 的依賴管理陷阱
useEffect(() => {
fetchData(id).then(setData)
}, [id]) // 漏寫依賴會導致閉包問題
虛擬 DOM 的效能開銷:雖然 React 18 引入了並發渲染和 Fiber 架構,但在高頻更新場景下,虛擬 DOM 的 diff 開銷依然不可忽視。
Solid、Svelte 等信號驅動框架在效能上表現出色,但它們的編譯時最佳化策略帶來了新的問題:
編譯產物不透明:編譯後的程式碼與原始碼差異巨大,除錯困難。 執行時能力受限:由於大量邏輯在編譯時完成,執行時的動態性和靈活性受到限制。
基於對主流框架的分析,我確定了自研框架 Vitarx 的三大核心設計理念:
設計原則具體含義優勢信號級精確更新資料變化時僅更新關聯的 DOM 節點無 diff 開銷,高頻更新場景效能優異執行時視圖樹保留完整的執行時結構,支援動態操作靈活的渲染編排能力萬物皆元件所有內建元件用公開 API 構建無黑盒,可客製化,可擴充### 3.2 分層架構
Vitarx 採用分層架構設計,各層職責清晰、依賴單向:
代碼解讀複製代碼┌─────────────────────────────────────────────┐
│ vitarx │ ← 聚合包,統一導出
├────────────┬─────────────┬──────────────────┤
│runtime-dom │ runtime-ssr │ │ ← 平台適配層
├────────────┴─────────────┼──────────────────┤
│ runtime-core │ utils │ ← 執行時核心 & 工具
├──────────────────────────┼──────────────────┤
│ responsive │ │ ← 響應式系統
└──────────────────────────┴──────────────────┘
響應式系統是 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,不需要重新渲染。
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
// ... 其他方法
}
問題:信號與 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] // 標記為"本次仍存取"
}
問題:在執行時維護完整的視圖樹結構,如何保證更新操作的高效性?
解決方案:引入視圖切換交易(ViewSwitchTransaction),將視圖切換操作封裝為可中斷、可回滾的交易。這使得 Transition 等元件可以在視圖切換過程中插入動畫邏輯。
typescript 代碼解讀複製代碼onViewSwitch((tx) => {
const { prev, next } = tx
tx.stopPropagation()
// 先執行離開動畫
runTransition(prev.node, 'leave', props, () => {
tx.commit() // 提交切換
// 再執行進入動畫
runTransition(next.node, 'enter', props)
})
})
問題:如何讓 Vitarx 使用自己的 JSX 編譯鏈路,而不依賴 React 的 jsx-runtime?
解決方案:透過 @vitarx/plugin-vite 插件配合 Vite 實現自訂 JSX 編譯。該插件的核心工作流程是:
jsx: 'preserve',讓建置工具不處理 JSXtransform 鉤子中實作 JSX → createView 的轉換邏輯v-if、v-else、v-model、v-show 等指令的處理Switch、IfBlock 等元件的編譯最佳化使用方式只需在 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 轉換開銷。
問題:如何實作伺服器端渲染和客戶端水合?
解決方案:實作 renderToString 和 renderToStream 兩個核心 API,同時在客戶端實作增量水合——只更新變化的部分。
typescript 代碼解讀複製代碼import { renderToString, renderToStream } from '@vitarx/runtime-ssr'
// 字串渲染
const html = await renderToString(App)
// 串流渲染
const stream = await renderToStream(App)
測試場景Vue 3.6React 19Vitarx 4.0建立1000行81.982.4112.2更新1000行88.284.8122.4部分更新(每10行)19.111.29.2選取行高亮9.85.84.5交換兩行10.767.610.4刪除一行20.918.117.2#### 記憶體配置對比(MB)
測試場景Vue 3.6React 19Vitarx 4.0初始記憶體0.861.180.83執行記憶體3.774.425.46建立/清理後1.231.971.16#### 傳輸大小對比
目標具體內容優先級生態完善推出官方 UI 元件庫 設計系統高VitaStack全端開發框架高工具鏈開發瀏覽器除錯插件高文件升級完善官方文件和教程中狀態管理開發官方狀態管理庫中### 6.2 中期目標
目標具體內容優先級跨平台支援推出行動端和桌面端支援中效能最佳化進一步最佳化首屏載入和渲染效能高### 6.3 長期目標
自研框架帶給我的不僅僅是一個可用的工具,更重要的是:
自研框架並非適合所有專案。以下場景更適合考慮自研:
如果你也在考慮自研框架,我的建議是:
寫在最後:Vitarx 是我兩年技術探索的結晶,它可能不是最完美的框架,但它是一個「看得見、摸得著」的框架。如果你也對前端框架的底層原理感興趣,歡迎加入我們的交流群一起探討。