🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付

Snapchat 開源全新跨平台框架 Valdi ,一起來搞懂它究竟有什麼特別之處

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

image

首先,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 的佈局引擎負責把這些聲明轉化為原生視圖的「尺寸/位置/層級」。

整個流程可以大致推論為:

  • TSX 寫組件 <view padding={10} backgroundColor="red"><label … /></view>
  • 在編譯/運行時,Valdi 的 TSX → 中間結構 → 原生視圖映射機制,會建立一個視圖樹(native views),但這些視圖的尺寸與位置不是簡單依賴平台預設佈局,而是由 C++ 佈局引擎計算
  • 佈局引擎接收節點樹、樣式(margin/padding/flex 等)
  • 引擎在主線程運行,計算每個視圖的尺寸和位置,輸出佈局結果,然後原生視圖被「inflate」/複用(Valdi 有全球視圖池化機制
  • 當數據狀態改變(組件 state 或 viewModel 更新)時,僅受影響的子組件重新渲染,其對應的視圖樹/佈局會被重新計算/更新,而不會強制父視圖全部重繪(「Components re-render independently without triggering parent re-renders」)

這裡有一些官方特別提到的點:

  • Automatic view recycling :一個 Global view pooling system,這個系統會複用現有的原生視圖物件(例如 UILabel,TextView),而不是創建新物件,從而減少原生控件 inflate/銷毀開銷
  • Viewport-aware rendering :只有可見的視圖節點才被 inflate,從而在滾動/大量列表場景中降低資源消耗,這也是 Snapchat 這樣的社交媒體應用最需要的能力
  • Optimized component rendering :Valdi 組件會「獨立地重新渲染,而不會觸發父組件的重新渲染」,從而實現快速、精細化的增量 UI 更新
  • marshalling overhead minimal :通過 C++ 佈局引擎減少跨語言通信成本

根據目前代碼看,Valdi 轉化的原生控件應該還是傳統 Android View 和 UIKit 體系,而 Valdi 的核心之一就是打磨自己的 C++ 佈局引擎,雖然用的原生控件,但是整個佈局流程都在 Valdi 自己內部實現,只有繪製流程回歸了平台,這也是為什麼 Snapchat 會說:「Flexbox layout system with automatic RTL support。」

Polyglot

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 的介面,然後在原生實現這個方法:

image

簡單來說,可以通過 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];

詳細可見:github.com/Snapchat/Valdi

當然,如果在 UI 層面,這還分為你想把 WebView 放到 UI 構建樹裡,還是僅僅顯示在用戶 UI 上方,如果是想把 WebView 嵌入到視圖結構裡,就需要創建一個自定義視圖,整體會更麻煩一些:

image

另外還有比如你需要一些平台調用,如藍牙、攝像頭,那麼 Valdi 也可以直接調用本地代碼,本地代碼也可以調用 Valdi 代碼:

  • 如果想要一個帶有嵌套即時攝像頭視圖的 Valdi 用戶界面,可以添加一個 <custom-view> 將它指向 iOS/Android 即時攝像頭視圖
  • 如果想啟用/禁用藍牙,可以原生處理此操作,然後公開一些函數,這些函數可以作為上下文的一部分傳遞給 Valdi 組件,然後 TS 代碼就可以根據需要調用它們

如果需要更形象的使用,具體代碼類似:

  • 在 TypeScript 調用 Native 代碼
/**
 * @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?.();
  }
}
  • 然後在 Kotlin 定義好原生實現
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")
}
  • 在 TypeScript 定義好介面
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);
    });
  }
}
  • 然後在 Kotlin 調用
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 實現上,可以讓開發者在已有原生 App 中逐步引入 Valdi,也可以在 Valdi 中插入原生視圖,這讓 Valdi 的接入和使用門檻相對變低不少,在混合開發領域有些許優勢
  • 背景處理:支持 worker 線程實現多線程執行,許開發者使用 worker 線程在 Valdi 中構建完整的功能,這也是一個有趣的點,比如和「Polyglot 模塊」結合,開發者可以在一個 TS worker 線程編寫業務邏輯,而線程自身又可以通過 "Polyglot" FFI 對 C++/Swift/Kotlin 代碼進行直接、零開銷的調用,類似更便捷版本的 Dart isolate Group/background
  • 支持 hot reload,不用多言,開發必備
  • 完整的 VSCode 調試:可以直接在 VSCode 中設置斷點、檢查變數、分析性能和捕獲堆轉儲

問題

那麼聊了這麼多,相信大家應該了解 Valdi 是怎麼實現的跨平台了,那麼該簡單聊聊它的問題了:

  • 社區待發展,雖然 Polyglot 很便捷,但是第三方平台插件總要有人寫和封裝,大部分開發者其實並不具備多平台的開發能力,所以社區插件生態完善是它未來是否可用的一個很重要的標誌
  • 文檔混亂不全,存在歧義也是目前的主要問題之一 ![image](https://i.imgur.com/lbeuWuS.jpeg
  • 開源還處於 beta 階段,例如目前 package.json 列出了 @snapchat/eslint-plugin-vivaldi 還是一個私有 NPM 依賴,類似的還有 Bazel 構建腳本失敗,問題也是構建腳本正試圖使用 SSH 從一個私有 GitHub 倉庫(名為 @valdi)抓取依賴,可以看出來本次開源還是相對倉促:image

所以,目前來看 Snapchat 確實有著出色的設計,比如自定義的 C++ 佈局引擎和 Polyglot,但它需要走的路還長,畢竟作為跨平台框架,它不再只是需要面對來自內部的壓力,Valdi 更需要得到社區的支持,並且有更多豐富的案例來幫助開發者投入其中,重要的是,需要讓開發者看到 Snapchat 持續維護開源的決心的行動。

那麼,你覺得 Valdi 如何?你會嘗試嗎?

參考鏈接


原文出處:https://juejin.cn/post/7572697146288177202


精選技術文章翻譯,幫助開發者持續吸收新知。

共有 0 則留言


精選技術文章翻譯,幫助開發者持續吸收新知。
🏆 本月排行榜
🥇
站長阿川
📝13   💬5   ❤️5
431
🥈
我愛JS
📝2   💬3   ❤️4
72
🥉
酷豪
1
評分標準:發文×10 + 留言×3 + 獲讚×5 + 點讚×1 + 瀏覽數÷10
本數據每小時更新一次
🔧 阿川の電商水電行
Shopify 顧問、維護與客製化
💡
小任務 / 單次支援方案
單次處理 Shopify 修正/微調
⭐️
維護方案
每月 Shopify 技術支援 + 小修改 + 諮詢
🚀
專案建置
Shopify 功能導入、培訓 + 分階段交付