最近看到好幾篇在推 Valdi 的文章,大致意思就是「RN/Flutter 的地位將受到威脅」,「Valdi 將成為全新的跨平台流行架構」云云,這不禁讓我好奇這個新框架有什麼魔力,還能在 2025 的跨平台領域玩出新花樣?

首先,Valdi 是由 Snapchat 開源的跨平台框架,其核心技術已在 Snap 的生產應用中驗證長達 8 年,號稱在不犧牲開發速度的前提下提供原生性能,那它是怎麼做到的?
簡單來說,Valdi 是一個 “用 TypeScript(TSX)寫 UI,然後編譯成原生視圖(iOS/Android/macOS)” 的跨平台 UI 框架,所以它說了 Valdi 不依賴 WebView 和 JS Bridge,也就是說 Valdi 就是一個將 TS 轉化為原生佈局的編譯支持?
答案肯定不是,這也是它和 RN 不同的地方,Valdi 是在編譯期完成的原生轉化。
雖然 Valdi 採用了「編譯時」模型,會將聲明式的 TS 直接編譯為平台原生視圖(iOS/Android/macOS),但是重點在於:
“Optimized layout engine – C++ layout engine runs on the main thread with minimal marshalling overhead.”
也就是說,雖然編譯成原生控件,但是它的佈局並不走原生佈局,它是通過自己的 C++ 佈局引擎來完成佈局的,當然其實新架構的 RN 現在也都有自己的佈局策略,比如 Fabric 取代了原本的 UI Manager,支持 React 並發渲染和 Suspense & Transitions。
而它另一個重要的特性就是 “Polyglot 模組”,這個模組核心就是實現一個自動類型安全綁定系統,它充當了一個編譯時外來函數介面 (FFI) 生成器,Polyglot 允許開發者「用 C++、Swift、Kotlin 或 Objective-C 編寫性能關鍵代碼」,允許開發者在 Valdi 中構建完整的功能(包括背景處理),從而消除了特定平台的橋接代碼。
實際上從社區上提及的,就是 Polyglot 在架構上優於 React Native (RN) 的 (JSI) 和 Flutter 的平台通道 (Platform Channels),理論應該和 Flutter 的線程合併後的 FFI 差不多。
所以,也就是搞懂了這兩個東西,我們就大概知道了 Valdi 是什麼了。
Valdi 佈局系統的底層支撐是一個「優化的 C++ 佈局引擎」,並且最有意思的是,這個引擎「在主線程上運行」,這也是這個框架的特色或者說爭議點。
對於這個問題,Valdi 表示這是一個優勢,理由是「最小化的編組開銷」,基礎是該 C++ 引擎的效率極高,以至於在 UI 線程上直接運行佈局計算的成本,低於將佈局數據發送到後台線程並異步接收計算結果(涉及數據編組)的總成本。
這其實是一種高風險又高回報的架構,這意味著 Valdi 的 C++ 效率必須極高且絕對可信。
而從這點也可以看出,Valdi 並不是直接調用系統的佈局機制(例如 iOS Auto Layout 或 Android ViewGroup 的 measure-layout),而是採用了自定義 C++ 佈局引擎來計算視圖尺寸與位置,可以簡單理解為:開發者以聲明式 TS 寫佈局,Valdi 的佈局引擎負責把這些聲明轉化為原生視圖的「尺寸/位置/層級」。
整個流程可以大致推論為:
<view padding={10} backgroundColor="red"><label … /></view>這裡有一些官方特別提到的點:
根據目前代碼看,Valdi 轉化的原生控件應該還是傳統 Android View 和 UIKit 體系,而 Valdi 的核心之一就是打磨自己的 C++ 佈局引擎,雖然用的原生控件,但是整個佈局流程都在 Valdi 自己內部實現,只有繪製流程回歸了平台,這也是為什麼 Snapchat 會說:「Flexbox layout system with automatic RTL support。」
Polyglot 是 Valdi 的原生集成解決方案的另外一個關鍵,也是 Valdi 最重要的架構特性,因為 Polyglot 可以在 TypeScript 和原生平台之間生成類型安全的綁定,通過綁定,可以讓 TS 和普通函數一樣與原生代碼雙向通信,允許「複雜的數據結構和回調」在 TS 和原生代碼之間安全傳遞。
並且 Polyglot 不是一個運行時橋接,而是一個編譯時 FFI 生成器,從 TS 到 myNativeModule.doThing() 的調用,都可以被編譯為對底層 Swift/Kotlin 實現的直接、類型安全、零開銷的同步函數調用。
這個實現主要是為了消除了困擾 RN 和 Flutter 集成的所有樣板代碼和序列化開銷,Snapchat 曾經開源的
djinni工具(用於生成 C++ 和 Java/Obj-C 之間的綁定)感覺大概率是該 Polyglot 的技術前身。
換言之,Polyglot Modules 在 Valdi 應用中,可以在需要性能或者原生平台特性(camera、硬體加速、第三方原生庫)時,用 C++/Swift/Kotlin/Obj-C 等語言編寫模組,然後通過自動生成的綁定,使得這些模組在 TS 層可以被安全調用。
舉個例子,比如關於 Webview 支持,雖然 Valdi 內部已經實現了這個功能,但它沒有包含在開源項目,用戶需要通過定義一個帶有 ExportProxy 的介面,然後在原生實現這個方法:

簡單來說,可以通過 Native 代碼將創建一個標記為 ExportProxy 的 TypeScript 介面實例,然後實例可以通過組件上下文傳遞給 TypeScript,具體為:
TypeScript:
// @Context
// @ExportModel({ios: 'SCMyComponentContext'})
interface Context {
serverURL: string;
}
// @Component
// @ExportModel({ios: 'SCMyComponentView'})
class MyComponent extends Component<any, Context> {
onCreate() {
// Will print http://api.server.com in the console
console.log(this.context.serverURL);
}
}
Native :
// Instantiate the context data structure
SCMyComponentContext *context = [[SCMyComponentContext alloc]
initWithServerURL:@"http://api.server.com"];
// Instantiate the view with the context passed as parameter
SCMyComponentView *view = [[SCMyComponentView alloc]
initWithViewModel:viewModel
componentContext:context];
當然,如果在 UI 層面,這還分為你想把 WebView 放到 UI 構建樹裡,還是僅僅顯示在用戶 UI 上方,如果是想把 WebView 嵌入到視圖結構裡,就需要創建一個自定義視圖,整體會更麻煩一些:

另外還有比如你需要一些平台調用,如藍牙、攝像頭,那麼 Valdi 也可以直接調用本地代碼,本地代碼也可以調用 Valdi 代碼:
<custom-view> 將它指向 iOS/Android 即時攝像頭視圖如果需要更形象的使用,具體代碼類似:
/**
* @Context
* @ExportModel({
* ios: 'SCYourComponentContext',
* android: 'com.snap.myfeature.YourComponentContext'
* })
*/
interface YourComponentContext {
callMeFromTS?();
}
/**
* @Component
* [...]
*/
class YourComponent extends Component<YourComponentViewModel, YourComponentContext> {
onMyButtonWasTapped() {
// Calls callMeFromTS: on the SCYourComponentContext (if it has been configured)
this.context.callMeFromTS?.();
}
}
package com.snap.myfeature.YourComponentContext
class SCYourComponentContextImpl {
val onDone: (() -> Unit)?
// ...
}
// So you can instantiate YourComponentContext and configure it with the callMeFromTS block:
val componentContext = YourComponentContext()
componentContext.callMeFromTS = {
// Will be called when this.context.callMeFromTS() is called in TS.
print("Hello from Kotlin")
}
interface YourComponentContext {
callMeFromTS?(completion: (arg: string) => void);
}
class YourComponent extends Component<any, YourComponentContext> {
onMyButtonWasTapped() {
this.context.callMeFromTS((arg) => {
console.log('the native code called the completion function with arg:', arg);
});
}
}
componentContext.callMeFromTSWithCompletion = { completion ->
// This will call the TS callback and provide the given value.
completion("I got you loud and clear");
}
可以看到,Polyglot 提供了一套非常方便的調用機制,這也是 Valdi 對自己性能如此有自信的來源。
關於 Valdi 的其他優勢還有:
那麼聊了這麼多,相信大家應該了解 Valdi 是怎麼實現的跨平台了,那麼該簡單聊聊它的問題了:
package.json 列出了 @snapchat/eslint-plugin-vivaldi 還是一個私有 NPM 依賴,類似的還有 Bazel 構建腳本失敗,問題也是構建腳本正試圖使用 SSH 從一個私有 GitHub 倉庫(名為 @valdi)抓取依賴,可以看出來本次開源還是相對倉促:
所以,目前來看 Snapchat 確實有著出色的設計,比如自定義的 C++ 佈局引擎和 Polyglot,但它需要走的路還長,畢竟作為跨平台框架,它不再只是需要面對來自內部的壓力,Valdi 更需要得到社區的支持,並且有更多豐富的案例來幫助開發者投入其中,重要的是,需要讓開發者看到 Snapchat 持續維護開源的決心的行動。
那麼,你覺得 Valdi 如何?你會嘗試嗎?