一、前言
在跨平台框架的讨论中,有一个看似矛盾的现象:
React Native 使用原生 UI 控件,Flutter 完全自绘,不使用原生控件。但大量开发者的实际体验却是——Flutter 更流畅。
这听起来有点反直觉。按照常规思维,直接调用系统原生控件应该更快才对。为什么”绕过”原生控件自绘的 Flutter,反而在性能表现上更胜一筹?
本文将深入两个框架的底层架构,从渲染链路、通信机制、动画性能等角度,客观分析为什么 Flutter 更容易保持 60fps,以及各自的优劣和 2022 年的选型建议。
二、React Native 的工作原理
React Native 的核心设计思想是:JavaScript 驱动原生 UI。
2.1 三线程模型
React Native 运行在三线程架构之上:
| 线程 | 职责 |
|---|---|
| JS 线程 | 执行 JavaScript 业务逻辑、React diff 算法、Virtual DOM 计算 |
| Native 线程(UI 线程) | 负责原生 UI 的布局、渲染与事件处理 |
| Shadow 线程 | 执行 Yoga 布局引擎,计算 flexbox 布局信息 |
2.2 Bridge 通信机制
三个线程之间的通信通过 Bridge(桥) 完成:
1 | JavaScript 逻辑 |
每一次跨线程通信,数据都需要经过 JSON 序列化与反序列化。这意味着:
- JS 线程计算出新的 Virtual DOM → 序列化成 JSON → 通过 Bridge → Native 线程反序列化 → 更新原生 View
- 用户触摸屏幕 → 生成 Native Event → 序列化成 JSON → 通过 Bridge → JS 线程处理
2.3 性能瓶颈在哪里?
Bridge 是 React Native 的阿喀琉斯之踵:
- 异步通信:Bridge 是异步的,这导致 UI 更新不是同步的,可能出现”视觉延迟”
- 序列化开销:复杂数据结构(如长列表 diff 结果)的序列化成本不可忽视
- Bridge 拥塞:当大量事件同时涌入 Bridge(比如快速滑动列表时),请求会排队等待,造成掉帧
- 单 JS 线程:所有 JavaScript 逻辑都在一个线程执行,复杂业务逻辑会阻塞 UI 更新指令的下发
这就是为什么 React Native 在复杂交互场景下容易出现”白屏闪过”或”响应迟钝”的根本原因。
Facebook 团队也意识到了这个问题,在 2018 年提出了 Fabric 新架构 和 JSI(JavaScript Interface) 试图从根本上解决 Bridge 瓶颈,但截至 2022 年初,新架构仍处于逐步推进阶段,尚未成为生产环境的默认选择。
三、Flutter 的工作原理
Flutter 走了一条完全不同的路——抛弃原生控件,全部自绘。
3.1 分层架构
1 | ┌─────────────────────────┐ |
3.2 三棵树模型
Flutter 内部维护三棵核心树:
- Widget 树:开发者编写的声明式配置(immutable,随时重建)
- Element 树:Widget 的实例化,负责 Widget 与 RenderObject 的连接
- RenderObject 树:真正参与布局和绘制的对象
当状态变化时,Flutter 重建 Widget 树,然后通过 diff 算法找到需要更新的 RenderObject,直接标记为”脏”,在下一帧绘制。整个过程都在 Dart 侧完成,不需要任何跨语言通信。
3.3 绘制流水线
Flutter 的渲染流水线(Rendering Pipeline)包含以下阶段:
1 | Layout → Paint → Composite → Rasterize |
所有阶段都由 Engine 中的 Skia 引擎直接调用 GPU API(Metal/iOS,OpenGL/Vulkan/Android)完成。这意味着:
- 没有 Bridge 通信:Dart → C++ Engine 的调用是直接的,无需序列化
- 绘制完全自控:每个像素都由 Skia 绘制,不存在平台 UI 控件的不一致问题
- 帧级精确控制:Flutter 可以精确控制每一帧的布局、绘制、合成时机
四、为什么 Flutter 更容易保持高帧率
4.1 渲染链路更短
RN 的渲染链路:
1 | JS 计算 Virtual DOM → JSON 序列化 → Bridge → JSON 反序列化 → Shadow 布局 → 原生 View 渲染 |
Flutter 的渲染链路:
1 | Dart 构建 Widget 树 → Framework diff → RenderObject 标记脏 → Skia 绘制 → GPU |
Flutter 少了 Bridge 通信这一整段链路。在 16ms 的一帧时间内,每节省一毫秒都很关键。
React Native 的 Bridge 通信延迟通常在 1-3ms 量级,加上序列化/反序列化,复杂场景下这个开销可能达到 5-10ms。当一帧只有 16ms 预算时,10ms 的固定开销就意味着只剩下 6ms 给实际渲染,掉帧几乎不可避免。
4.2 没有频繁 Bridge 通信
React Native 的每一次 UI 更新都是一次 Bridge 往返。在以下场景中,Bridge 通信频率极高:
- 长列表快速滚动:每个新 item 的创建都需要 Bridge 通信
- 动画:每帧需要从 JS 发指令到 Native
- 手势交互:高频触摸事件不断涌入 JS 线程
Flutter 在这类场景中全部在 Dart/Engine 层完成,零 Bridge 通信。
4.3 UI 一致性更好
React Native 依赖平台原生控件,这意味着:
- iOS 上是 UIView,Android 上是 View
- 同一套代码在不同平台可能渲染出不同效果
- 某些组件在不同平台上的行为差异需要大量 patch 代码
Flutter 自绘所有 UI,真正的 “所见即所得”。iOS 和 Android 上的像素完全一致,消除了因平台控件差异导致的视觉不一致问题。
4.4 动画性能优势
Flutter 的动画系统在设计之初就考虑了 60fps:
- 动画在 UI 线程执行,不受 Dart 业务逻辑影响
- 使用
vsync机制与屏幕刷新率同步 - 动画插值计算在 Engine 层完成,效率极高
React Native 要实现流畅动画,需要使用 useNativeDriver: true 将动画配置发送到 Native 侧执行。但一旦动画涉及非 NativeDriver 支持的属性(如 width、height),动画就会回退到 JS 线程,卡顿随之而来。
4.5 跨平台成本更低
这里说的”成本”不仅是代码复用率,更是性能优化成本。
React Native 开发者经常需要:
- 排查 Bridge 瓶颈
- 优化 JS 线程负载
- 手写 Native Module 处理性能热点
Flutter 开发者通常不需要关心这些底层问题,因为框架已经消灭了这些瓶颈。省下的调优精力可以投入到业务开发中。
五、实际案例分析
案例一:长列表滚动
React Native 的瓶颈:
FlatList 在快速滑动时,大量 item 需要被创建和回收。每个新 item 的创建都涉及 JS → Bridge → Native 的通信。如果列表项复杂,JS 线程的计算压力和 Bridge 通信延迟叠加,容易导致白屏和掉帧。
Flutter 的解决方案:
ListView.builder 在滑动时,Dart 可以直接创建新的 RenderObject 并交给 Skia 绘制。整个过程在 Engine 线程完成,没有跨语言开销。即使列表项复杂,只要布局和绘制能在 16ms 内完成,就不会掉帧。
案例二:页面切换动画
React Native 的瓶颈:
使用 react-navigation 做页面切换动画时,如果动画未开启 useNativeDriver,每帧都需要通过 Bridge 发送新的 layout 属性,掉帧风险极高。即使开启了 useNativeDriver,也只能覆盖 opacity、transform 等有限属性。
Flutter 的解决方案:
Hero 动画、PageRouteBuilder 等全部在 Engine 层处理。开发者可以定义极其复杂的页面转场动画,而不用关心的性能问题——因为绘制都由 Skia 直接完成。
案例三:实时数据刷新
React Native 的瓶颈:
当 WebSocket 高频推送数据(如行情数据)需要更新 UI 时,每条数据都需要:
- JS 线程处理数据、更新 state
- Virtual DOM diff
- 序列化更新指令
- Bridge 通信
- Native 更新 UI
高频数据场景下,Bridge 极易拥塞。
Flutter 的解决方案:
Dart 的单线程事件循环处理数据,setState 触发 Widget 重建,Framework 的 diff 算法找到最小更新集,直接通知 RenderObject 重绘。整个流程在 Dart 侧闭环。
案例四:复杂界面渲染
React Native 的瓶颈:
当界面层级深、组件嵌套多时,JS 线程的 diff 计算量指数级增长。大量 diff 结果通过 Bridge 传输,延迟累积明显。复杂界面首次渲染的白屏时间往往较长。
Flutter 的解决方案:
Flutter 的 RenderObject 树是扁平化的,布局和绘制采用深度优先遍历,算法复杂度可控。首帧渲染直接构建三棵树并绘制,没有中间环节。
六、Flutter 的缺点
客观评价,Flutter 并非完美:
6.1 安装包体积
Flutter Engine 被编译进 APK/IPA 中,会显著增大安装包。一个简单的 Hello World 应用,Flutter 版本比原生版本大约 4-5MB(Android)。对于安装包体积敏感的项目(如海外新兴市场),这是不可忽视的代价。
6.2 原生生态隔离
由于不使用原生控件,Flutter 无法直接复用现有的原生 UI 组件库。如果需要调用平台特有功能(如 ARKit、指纹识别等),仍需通过 Platform Channel 进行通信——这又回到了类似 Bridge 的模式。
6.3 学习成本
Dart 语言虽然简洁,但生态和社区远不及 JavaScript/TypeScript。团队需要有成员愿意学一门新语言,这在招聘和技术储备上都是成本。
6.4 平台适配问题
自绘 UI 意味着 Flutter 需要自行处理平台差异,如:
- iOS 回弹效果(虽然已内置
BouncingScrollPhysics) - Android 返回键行为
- 各平台字体渲染差异
- 输入法、无障碍等系统级功能的适配
这些细节处理不到位时,用户会有”非原生感”。
七、React Native 的优势
React Native 依然有其不可替代的优势:
7.1 原生控件
使用原生控件意味着:
- 系统升级时,UI 自动获得新特性(如 iOS 15 的新导航栏样式)
- 无障碍功能天然支持
- 与系统 UI 风格保持一致
7.2 Web 开发者上手快
JavaScript + React 的技术栈意味着海量的 Web 前端开发可以直接投入移动端开发。团队组建成本低,人才储备充足。
7.3 成熟生态
截至 2022 年,React Native 拥有 npm 海量第三方库的加持,社区贡献的组件和工具链非常丰富。Expo 这样的平台更是大大降低了上手门槛。
7.4 热更新能力
CodePush 等方案让 React Native 可以实现线上热修复和动态更新(绕过 App Store 审核)。这对需要快速迭代的业务非常重要。Flutter 虽然有 Code Push 的社区方案,但风险和稳定性远不如 React Native。
八、2022 年该如何选择
没有最好的框架,只有最合适的框架。选型的核心不在于”什么项目”,而在于”什么人做”。一个简单的判断逻辑:
如果你的团队是移动端(iOS / Android)背景 → 优先推荐 Flutter。
Flutter 的开发范式天然贴近客户端开发:自绘 UI、Widget 树、RenderObject 这些概念与原生 View 体系有相通之处。iOS/Android 开发者转 Flutter 的学习曲线远低于学 JavaScript + React 生态。
如果你的团队是前端(React / Vue)背景 → 优先推荐 React Native。
React Native 直接复用前端技术栈,组件化思想、JSX 语法、npm 生态无缝衔接。团队几乎可以零成本启动移动端开发。
归根到底,框架只是工具,生产力的决定因素是团队的已有积累。让 iOS 开发去写 JavaScript,让前端去写 Dart,都是增加不必要的摩擦成本。
总结
回到开头的问题:为什么”不使用原生控件”的 Flutter 比”使用原生控件”的 React Native 更流畅?
答案可以浓缩为三个字——少走弯路了。
React Native 在 JavaScript 和 Native 之间搭建了一座桥,但这座桥成了性能瓶颈。Flutter 直接把路铺到了 GPU,中间没有收费站。
但架构优势不等于无脑选择。React Native 的生态成熟度、团队组建成本、热更新能力依然是 Flutter 短期内难以企及的。2022 年做技术选型,还是要回到业务场景、团队能力、长期维护成本这三个维度上做权衡。
扫描二维码,分享此文章