Snapchat Valdi:用声明式 TypeScript 直达原生视图的跨平台性能方案
引言:跨平台开发的基本矛盾与 Valdi 的定位
跨平台开发的长期难题在于“速度与性能难以兼得”:一端是 Web 技术栈带来的快速迭代与高复用,另一端是原生技术栈提供的系统级性能与交互 fidelity(逼真度)。业界在二者之间尝试了多种折中:以 WebView 为内核的混合方案、以 JavaScript 桥接为主的“原生外观”方案,以及近年来以 Dart/Flutter 为代表的“自绘引擎”路线。这些方案或是牺牲了系统控件的一致性与性能,或是在复杂 UI、动画和手势上存在“最后一公里”的差距。
Valdi 的思路是反其道而行:不把 Web 技术藏进壳里,也不引入独立渲染引擎,而是在语言层直接向原生靠拢。它用声明式 TypeScript(TSX)描述界面,由编译器生成 iOS、Android 与 macOS 的原生视图,不使用 WebView,也不通过传统 JavaScript 桥接进行交互。Valdi 并非概念验证——官方说明其已在 Snap 的生产应用中使用八年,当前以“beta”开源,是因为希望先在开源生态中打磨工具链与开发体验,再择机退出 beta。这篇文章将聚焦其“原生性能如何实现”“跨平台一致性如何达成”“开发者体验如何提升”三条主线,拆解 Valdi 的技术架构与工程化方案,并给出迁移与性能优化的可操作清单。
来源:Valdi GitHub 仓库与官方文档。
架构总览:从声明式 TypeScript 到原生视图
Valdi 的工作流可以概括为:开发者用 TSX 描述组件的声明式 UI,Valdi 的编译链将其转为平台原生视图(UIView/UINavigationController、android.view.View、NSView 等),并以类型安全方式调用原生 API。渲染路径中不使用 WebView,也不依赖传统的 JavaScript 桥接,因而在交互路径、事件分发与布局计算上尽可能压缩了中间层开销。
这一架构选择背后有两点值得强调。第一,视图的“按需生成”与“增量更新”由框架控制:组件重渲染不级联触发父级重渲染,天然利于小步快跑的界面更新。第二,布局引擎以 C++ 实现,运行在主线程,但通过减少跨语言边界的数据复制(marshalling)来控制开销。对于长期运行的移动应用,这种“少而精”的桥接设计,能在维持原生控件行为一致性的同时,降低 UI 层的“固定摩擦成本”。
类型安全绑定与自动代码生成
Valdi 并不把“桥接”看作临时补丁,而是纳入编译链路的一等公民:TypeScript 接口通过注解编译为 Swift、Kotlin、Objective-C 的绑定,开发者可以以类型安全方式调用原生 API,包括第三方库。这一能力与“Polyglot 模块”结合,形成“通用 TS 层 + 专用原生实现”的协作模式:对性能敏感的逻辑(如图像处理、复杂动画)用 C++/Swift/Kotlin 实现,通过自动生成的绑定暴露给 TS; TS 负责视图组织、数据编排与跨平台一致性。双向通信在结构体与回调传递上也有约束,避免“任意对象”穿越边界带来的序列化与内存成本。
灵活嵌入模型:降低迁移门槛
Valdi 支持两种渐进式集成路径:其一是“Valdi in Native”,将 Valdi 组件直接嵌入既有 UIKit/Android 视图体系,适合在局部页面或功能域试点;其二是“Native in Valdi”,通过 在 Valdi 布局中插入原生控件,适合在统一布局下复用既有成熟组件。两者结合,让团队可以以“分屏/分模块”方式逐步引入,无需一次性重写整个 App。
性能实现:把原生性能落到具体技术点
在移动端 UI 体系里,性能不是抽象概念,而是几个“硬指标”的组合:列表滚动的帧率、视图创建与回收的延迟、布局计算与主线程可用性、动画与手势的响应性,以及长列表的内存足迹。Valdi 的性能策略围绕这五个维度展开。
首先,Automatic view recycling(自动视图回收)是一个跨屏幕的全局视图池。列表、滑出页以及任意可回收的视图类型在用户看不到时并不销毁,而是回收入池;当再次需要时直接复用。这不仅减少了 inflation(视图创建)带来的峰值延迟,也降低了短时频繁销毁/创建带来的 GC 抖动与内存碎片。
其次,组件重渲染的粒度被限制在“独立更新”范围:子组件的 state 或 props 变化不会引发父树重渲染,变相提高了增量更新的可控性。在复杂表单或动态信息流中,这种“就地最小更新”比“整树 diff”更接近原生开发的直觉。
第三,C++ 布局引擎直接运行在主线程,这一选择看似违背“多线程更快的直觉”,但对 UI 场景却更务实:跨线程引入的上下文切换与数据传输,可能吞噬主线程节省的 CPU 时间。Valdi 通过尽量减少跨语言边界的 marshalling 来控制成本,让布局计算以更直接的方式占用主线程时间片。
第四,Viewport-aware rendering(视口感知渲染)只 inflate 可见视图。对无限列表、社交信息流、搜索建议等典型场景,这等于把“潜在无限数据”压缩在“可见窗口”内,显著减少首次渲染压力与滚动过程中的对象创建。
最后,原生动画与手势由系统原生路径承载,不以脚本驱动的仿制动画替代。因此,动画曲线、弹性效果、手势并发与冲突处理都遵循平台既有行为,这也是“原生性能”最直观的体现。
为便于对照,下表将 Valdi 的关键技术点与性能指标进行映射。
表1 关键技术点与性能指标映射
| 技术特性 |
影响的性能指标 |
预期收益 |
适用场景 |
| Automatic view recycling |
inflation 延迟、GC 抖动、内存峰值 |
降低创建/销毁成本、稳定帧率 |
列表、滑出层、卡片池 |
| 组件独立重渲染 |
增量更新耗时 |
减少不必要更新、提高响应性 |
表单、动态内容 |
| C++ 布局引擎(主线程) |
布局计算耗时 |
降低跨边界成本、直接利用主线程 |
复杂嵌套布局 |
| Viewport-aware 渲染 |
首屏时间、滚动流畅度 |
仅 inflate 可见视图、降低内存足迹 |
无限列表、Feed |
| 原生动画与手势 |
动画/手势响应性、曲线 fidelity |
遵循系统行为、减少脚本开销 |
转场、交互控件 |
上述机制并非孤立,它们共同收敛于一个目标:把 UI 的“瞬时响应”交还系统路径,把“计算密集”交给原生或本地模块,避免“看似跨平台、实则双重折衷”。
来源:Valdi README 与 Performance 文档。
开发者体验:从“编写-调试-发布”的端到端加速
Valdi 的开发体验围绕“减少等待”来设计。Instant hot reload 让内环反馈从“分钟级”缩短到“毫秒级”,尤其在 UI 调参与状态对齐时,基本去掉“重启-导航-复现”的等待成本。调试方面,Valdi 与 VSCode 深度集成,支持断点、变量检查、性能剖析与堆转储,基本覆盖了传统原生开发中常用的定位手段,也保留了跨平台统一工作流的好处。
在语法与工具链上,TSX 与 TypeScript 的组合既保留了声明式 UI 的表达力,又提供了静态类型检查与 IDE 辅助,减少“名字打错—运行期才报错”的低效反馈。对于已有前端背景的团队,迁移心智成本相对可控。
值得补充的是 Worker threads:在 JS 层引入多线程,用于后台计算与 I/O,减少主线程阻塞。这对长列表预取、图片解码或复杂数据转换尤为有用,可以在不牺牲响应性的前提下处理“重量级任务”。
跨平台一致性与原生能力:如何“一套代码,多端一致”
一致性来自两个层面:一是由框架统一生成与管理的原生视图与渲染流程,二是平台差异的显式建模与编译期抽象。
在布局方面,Valdi 提供了 Flexbox 布局系统,并内建 RTL(从右到左)支持,减少针对不同书写方向的分叉代码。动画、手势与触摸系统由原生路径承载,开发者可以使用平台原生行为,而无需用脚本模拟一套“近似”效果。
在平台特性暴露上,Valdi 的策略是“类型安全直达原生”:通过自动生成的绑定与 Polyglot 模块,TypeScript 层可以调用平台 API 与第三方库,避免“由 JS 桥接层代理”的二次封装。数据侧,Valdi 还支持原生 protobuf,用于高效序列化与跨模块通信。
表2 平台支持矩阵(按官方公开能力汇总)
| 维度 |
iOS |
Android |
macOS |
| UI 框架支持 |
原生 UIView/导航等 |
原生 View 体系 |
原生 NSView |
| 布局系统 |
Flexbox(含 RTL) |
Flexbox(含 RTL) |
Flexbox(含 RTL) |
| 动画/手势 |
原生 |
原生 |
原生 |
| 调试 |
VSCode 断点/剖析/堆转储 |
VSCode 断点/剖析/堆转储 |
VSCode 断点/剖析/堆转储 |
| 原生 API 调用 |
类型安全绑定 + Polyglot 模块 |
类型安全绑定 + Polyglot 模块 |
类型安全绑定 + Polyglot 模块 |
| Worker 线程 |
支持 |
支持 |
支持 |
| 嵌入模型 |
Valdi in Native / Native in Valdi |
Valdi in Native / Native in Valdi |
Valdi in Native / Native in Valdi |
| 文档与示例 |
官方文档覆盖 |
官方文档覆盖 |
官方文档覆盖 |
注:桌面端的覆盖范围与能力边界以官方仓库与文档为准,迁移前建议先对目标版本做兼容性验证。
与 React Native/Flutter/NativeScript 的差异
跨平台方案的比较,核心在于“看齐谁”的哲学:是看齐 Web 的生态,还是看齐原生的系统路径;是引入独立渲染引擎,还是直接生成原生控件;是选择高层抽象,还是以工具链消解差异。
- 与 React Native(RN)相比,Valdi 不依赖 JavaScript 桥接,也不以“原生外观”的组件作为目标,而是直接在编译时生成原生视图。换言之,RN 的“JS 驱动原生控件”在 Valdi 里被“声明式 TS 直接生成原生视图”替代,减少一层动态分派与消息往返。
- 与 Flutter 相比,Valdi 不引入 Skia 等自绘引擎,而是复用各平台原生控件,因此在 UI 一致性上更偏“随平台”,而非“在所有平台画成一样”。这有利于把平台的交互与系统特性“原样继承”,同时避免维护一套跨平台自绘引擎的长期成本。
- 与 NativeScript 相比,Valdi 的声明式 TSX 到原生视图的编译路径更直接,减少对“运行期解释 + 反射式桥接”的依赖;同时,Valdi 把“类型安全绑定、布局、渲染、动画、手势”纳入统一工程体系,由工具链在编译期做更多确定性的事。
表3 框架对比概览
| 维度 |
Valdi |
React Native |
Flutter |
NativeScript |
| 渲染路径 |
TSX 编译为原生视图 |
JS 驱动原生控件(桥接) |
自绘引擎(Skia) |
JS/TS 运行时 + 原生 API 反射式调用 |
| 是否 WebView |
否 |
否 |
否 |
否 |
| 是否引入自绘引擎 |
否 |
否 |
是 |
否 |
| 布局系统 |
Flexbox(带 RTL) |
布局 Yoga 等 |
自身布局体系 |
CSS/Flexbox 等 |
| 动画/手势 |
原生 |
原生 + JS 驱动 |
自绘 + 原生封装 |
原生 |
| 语言栈 |
TypeScript(TSX) |
JavaScript/TypeScript |
Dart |
JavaScript/TypeScript |
| 热重载 |
支持(毫秒级) |
支持(Fast Refresh) |
支持(Hot Reload) |
支持 |
| 原生 API 调用 |
类型安全绑定 + Polyglot |
Bridge/Native Module |
平台通道(Platform Channels) |
反射式 + 插件 |
| 桌面支持 |
macOS(官方) |
Windows/macOS(社区成熟) |
桌面实验/成熟中 |
以移动为主(有生态扩展) |
结论并非“此优彼劣”,而是“取舍不同”:如果你强调“平台原生行为 + 声明式开发 + 编译期更多确定性”,Valdi 的取舍会更合适;如果你需要 Web 生态和 RN 经验,或是自绘可控的视觉一致性,Flutter 是另一种答案;如果你偏好“JS 直接戳原生 API”的简单直接,NativeScript 也是合理方案。
来源:Valdi 官方文档与对比信息(结合仓库特性说明整理)。
工程化实践:测试、调试、构建与监控
工程化的成功标准,不是“能否跑起来”,而是“能否稳定地持续交付”。Valdi 的工程化栈围绕“内环快、反馈准、构建稳、线上可控”四个目标展开。
在测试上,Valdi 提供组件级单元测试,便于对渲染与状态更新做细粒度验证,配合本地 Mock 与数据构造,快速定位“渲染行为与数据流不一致”的问题。调试层面,VSCode 断点、变量检查与堆转储已覆盖大部分定位需求,结合性能剖析,可在“卡顿/峰值/泄漏”三个方向形成闭环。
构建侧,Valdi 提供 Bazel 集成,支持可重现与增量构建。移动团队的痛点通常不是“第一次构建慢”,而是“每天成百次局部变更带来的等待与不确定性”。Bazel 的缓存与产物复用机制,能把“二次构建”拉回秒级,显著提升大型工程的节奏感。
上线后,建议从“首屏/列表帧率/内存/卡顿/崩溃/ANR”六大指标做持续观测。对列表场景,重点关注滚动丢帧率与内存峰值;对动画与手势,关注交互延迟与掉帧集中区域;对崩溃与 ANR,结合符号化堆栈与系统日志,追踪“原生—JS 边界”附近的异常。
风险与局限:采用前的评估清单
- Beta 状态:尽管已在生产使用八年,Valdi 开源仍处 Beta,工具链与文档的社区化打磨尚需时间。团队需要有“跟随迭代”的准备与人力冗余。
- 团队技能结构:TypeScript 与原生语言(Swift/Kotlin/C++)的协作模式要求两端都有人能“接住球”,尤其是性能敏感模块与调试场景。
- 插件生态成熟度:相比 RN/Flutter,Valdi 的社区插件数量与覆盖度可能存在差距,迁移时需盘点第三方依赖的可替代性或自研成本。
- 桌面端与高级特性:虽然支持 macOS,但覆盖范围与边界以官方为准,涉及平台特性、窗管权限或系统集成的场景需做 PoC 验证。
- 构建与发布管线:Bazel/VSCode/多平台编译的链路对 CI/CD 提出新要求,建议提前对齐缓存策略、构建机规格与符号化流程。
来源:Valdi 官方状态与文档。
落地建议与迁移路径:从试点到规模化
- 从“边角料”功能试点:选取独立性强的功能页,采用“Valdi in Native”接入,以一周为周期完成端到端打通(热重载—调试—构建—发布),验证收益与工程约束。
- 对性能热点做 Polyglot 替换:将图像处理、复杂列表渲染、复杂动画等抽象为 C++/Swift/Kotlin 模块,TS 层通过类型安全绑定调用,确保瓶颈逻辑在原生路径完成。
- 渐进式替换与双轨验证:对关键页面采用“Native in Valdi”复用既有原生控件,保留“可比对”的旧实现,进行 A/B 或灰度,观察性能与体验指标再决定是否全面替换。
- 建立跨语言开发规范:包括类型定义与注解、绑定生成流程、错误与异常处理、测试与发布策略,避免“桥接层”成为隐性技术债。
- 开发者赋能:以“内环加速(热重载/调试)—性能可观测—问题定位”为主线做培训与模板沉淀,确保团队在“写—测—调—发”各环节形成一致预期。
结论:Valdi 的价值边界与适用场景
Valdi 的价值主张非常清晰:用声明式 TypeScript 直达原生视图,把“跨平台开发速度”与“原生性能”两端的收益同时兑现;在关键路径上以类型安全绑定与 Polyglot 模块保障“性能/工程化/可维护性”的三角平衡。对于需要快速迭代、强调原生交互 fidelity(尤其是动画与手势)、团队具备 TS 与原生能力混合结构的移动/桌面应用,Valdi 提供了值得尝试的路径。
它的边界同样明确:社区生态尚在打磨,Beta 标签意味着工具链与文档需要时间沉淀;在生态厚度与跨平台 UI 一致性上,Valdi 选择“随平台”而非“自绘统一”,这既是性能与行为 fidelity 的优势,也是对 UI 视觉完全一致性的取舍。
下一步观察点包括:生态插件的丰富度、桌面端支持的扩展、开发者工具的成熟,以及在超大规模场景中的案例沉淀。对于正在评估“能否用一套代码,在多端同时获得原生体验”的团队,Valdi 值得进入候选名单,并通过一个双周 PoC 验证其在你所在业务中的真实 ROI。
参考资料: