做技术选型是最容易吵起来的事情之一。React Native、Flutter、UniApp、小程序这几套跨端方案几乎每次讨论都能吵出几百楼吵到最后往往是谁都赢不了。我在最近一个模拟项目X里要同时覆盖公众号、小程序和两个系统的App端干脆把四套方案都拉出来做了原型验证顺便把团队里大家关心的性能、热更新、开发效率挨个测了一遍。这篇就是那次验证的整理版会先讲清楚原理层面的差异再做横向对比最后给一个实际可用的选型建议。不管你是准备接外包、做创业产品还是在公司内部推动技术栈统一这篇都能帮你少做几次无效实验。1. 先把原生渲染这件事说清楚RN 与 Flutter 的根本差异1.1 RN 的桥接机制和性能边界很多 RN 性能问题的根源是“桥接”这两个字。RN 的逻辑跑在 JavaScript 引擎里但它最终要渲染出来的控件是原生控件比如 Android 的 TextView、ButtoniOS 的 UILabel、UIView。这两边不是一个世界所以信息要在 JS 线程和原生线程之间来回传递传递走的就是 Bridge。可以把它想象成外宾和本地人的翻译偶尔见一次面没问题但高峰期每见一个人都要经过翻译队伍一长自然就慢。RN 里那种快速滚动的长列表、输入框连续输入、边滚动边更新状态的操作都在制造“翻译高峰”。需要先纠正一个认知RN 不是 H5 套壳。在 React Native 出现之前很多团队用 Cordova 或 WebView 方案做混合那种方案是把整个 H5 页面放进 WebView 渲染性能瓶颈在浏览器引擎。RN 则只把标签翻译成原生控件业务逻辑用 JS 控制所以整体响应比传统 hybrid 好一个量级。这也是为什么到现在仍有人愿意用 RN 的原因它能和原生 UI 保持高度一致输入框、摄像头、地图这类系统底层能力不用自己造轮子天生就有原生的体验。但桥接的存在让 RN 的上限被锁死。尤其是旧架构里JS 调用原生 API、原生回调 JS 都要序列化如果业务中频繁做同步操作界面就会出现肉眼可见的掉帧。我实测过一个数据在低端 Android 设备上同时滚动列表和更新右上角标题RN 普遍会比 Flutter 掉更多帧而且掉帧都发生在桥接密集的时刻。RN 社区也意识到这个问题新架构已经在用 JSI 取代桥接的一部分工作让 JS 能直接持有原生对象引用减少序列化。但新架构落地不等于所有库都能立刻适配老项目要迁移仍然是一个不小的工程。实操层面如果项目已经选了 RN可以从几个地方优化长列表用 FlatList 的时候把 getItemLayout 和数据穿透都设置好不要让每个 row 都重新渲染高频输入框给 onChangeText 加节流等用户停顿再更新状态尽量避免在 JS 里用 setInterval 驱动 UI 动画改用 Animated 或 Reanimated。这些优化不复杂但在真实项目里能挽回很多帧。1.2 Flutter 自绘引擎为什么能实现体验统一Flutter 从原理上就走了一条完全不同的路。它不做“翻译”而是自己在画布上画 UI。Flutter 引擎里内置了 Skia新版本已经逐步换成 Impeller开发时用 Dart 描述一套 WidgetUI 组件不是映射到原生控件而是由引擎直接渲染到当前设备的一个 canvas 上。所有平台拿到的是同一套绘制结果所以你几乎不会看到“iOS 上很圆润Android 上突然切口”这种平台差异这是自绘机制带来的最大优势。为什么自绘会更流畅因为 UI 的每一帧都在 Flutter 自己掌控的渲染流水线里完成不需要经过系统控件也不用频繁跨线程传递消息。你可以把它想象成一支自带画材的乐队到哪个场地都用自己的乐器演奏RN 则是跟当地乐队借设备借设备和调试设备都要花时间。所以在动画、转场、列表滚动这些场景Flutter 通常能获得更稳定的帧率这也是很多团队愿意忍受 Dart 学习成本的核心原因。但自绘不是没有代价。第一个代价是包体积和启动时间Flutter 要打包自己的引擎一般情况下一个空壳 App 的包体就要比原生大十几兆旧手机首次启动还会有一个引擎初始化的空白窗口。第二个代价是原生控件只能用“外挂”的方式接入比如地图、视频播放、WebView 这类复杂原生组件Flutter 没法直接照搬必须通过 Platform Channel 做一个原生插件桥。这个桥只在需要的时候才建立但一旦建立问题就容易比 RN 更多因为要维护两端的原生代码和 Dart 代码。也不要神化 Flutter 性能。Flutter 对开发者有明确的纪律要求不用 const、不正确地控制 State 范围、频繁重建大型 Widget 树照样会掉帧。真实项目里用 Flutter 开发重点不在框架本身而在状态管理和渲染优化。把 Provider 或 Riverpod 这类状态方案选对通常比纠结引擎版本更重要。2. UniApp 和小程序站在 Web 语言的肩膀上的另一条路线2.1 UniApp 的原理编译到多端UniApp 在我看来是“DSL 编译器 运行时适配”的结合体。它用 Vue 语法写代码编译期会根据目标平台生成对应代码在小程序端生成小程序代码在 H5 端生成普通 Web 页面在 App 端生成一段可被原生容器加载的代码。看起来一劳永逸很美好但事实上每一跳都不是零成本。比如同样一个可滚动区域在小程序里它要被转成 scroll-view在 App 里可能走的是 WebView 渲染在 H5 里就是 div 加 overflow。不同容器的渲染方式不同性能边界也就天然不同。很多团队前期用 UniApp 会发现开发效率极高表单、列表、数据展示、登录注册这些常见功能几乎几天就能出第一版。为什么会这么快因为它把 Vue 的生命周期、响应式系统、组件体系全部平移过来了前端团队可以完全不碰原生代码直接用一个熟悉的框架写业务。对于快速验证商业模型、做内部工具、做需要同时覆盖 App 和小程序的轻量产品来说这个效率优势非常关键。但 UniApp 的短板也很明显。第一它要兼容的平台越多边界条件越多。在开发者工具里一切正常真机上一跑却出现一堆样式和事件问题这类情况非常常见。第二复杂的用户交互比如拖拽排序、手势缩放、半屏弹窗带动画在 UniApp 里写到 App 端往往会发现性能跟不上最后还是得自己写原生页面或者找原生插件。所谓“一套代码多端复用”在复杂 UI 场景下经常变成“一套代码多处调试”这也是网上对 UniApp 评价两极分化的原因。我的建议是用 UniApp 之前先明确业务的核心复杂度在哪里。如果核心都在表单、数据展示、流程流转那就放心用如果核心是在图形编辑、实时地图、复杂手势建议换 Flutter 或原生或者为这些重模块单独做原生插件不让 UniApp 一肩挑。2.2 小程序的双线程模型与运行瓶颈小程序并不是一个浏览器页面而是一个双线程系统。逻辑层JS 逻辑和渲染层WXML/WXSS 渲染是分开的两者通过底层消息通道同步数据。开发者在 JS 里执行 setData数据要先序列化再传给渲染层渲染层拿到新数据之后更新视图这个过程是异步的。为什么不一开始就做成单线程主要是隔离安全和保证稳定避免不可信的页面脚本直接操作宿主环境。可是一分隔离一分代价通信变成运行时的最大瓶颈。在实际开发里最常见的问题是“一次性 setData 太多”。比如请求回来 100 条数据直接 this.setData({ list: res })页面很可能闪一下或者滚动僵硬。因为渲染层要重新创建大量节点同时通信线程要复制、解析大对象性能自然掉下来。在 RN 和 Flutter 里这样的更新通过虚拟节点 diff 或自绘重绘成本相对可控而在小程序的双线程模型里大对象的序列化开销就是不可忽略的瓶颈。那么怎么破比较好的实践是维护一个本地数据缓存渲染层只展示当前视口需要的条目。列表可以用分页加载每次只 setData 增量数据和加载状态。搜索功能不要每次输入都向渲染层传全部过滤结果而是在本地 JS 内存里先过滤再截取 TOP N 条。界面上的高频状态比如进度条、拖拽位置最好用 WXS 响应事件或 CSS 动画而不是频繁 setData。小程序的优化思路非常接近后端做数据传输减少包体、减少通信、合并频繁变更。2.3 从 Web 开发转过来的成本差异如果团队已经是前端技术栈选型成本差异其实是最大的一个考虑项。同样是“前端团队”从 Web 转 UniApp 几乎是无缝的因为 UniApp 就是 Vue 的语法和思路原来用的 Vue 组件、Vue Router、Pinia 都能继续用。转小程序则需要额外学习 WXML 和 WXSS 的标签和样式规则但整体思维跟写 HTML/CSS 很像一到两周就能上手。转 RN 则要求你理解 React 的组件模型和状态管理还得有原生环境下的调试经验尤其是遇到原生崩溃的时候只懂 Web 的人会比较痛苦。转 Flutter 的学习成本最高Dart 语法本身不难难的是要把“DOM 树”的思维切换成“Widget 树”的思维要理解 StatelessWidget 和 StatefulWidget 的重建机制。这里可以直接给一个难易度排序UniApp 小程序 RN Flutter。但难度低不等于上限高难度低对应的是开发快、维护累难度高对应的是前期投入大、出活慢、可折腾的上限高。选型时不要只看“几天能上手”还要看“这个团队未来的竞争优势是什么”。如果团队只擅长写表单Flutter 未必能帮上忙反过来如果产品核心壁垒就是高质量的图形交互那前期的学习成本反而应该被视作投资。3. 四套方案横向对比选型时真正该盯住的几个维度3.1 开发语言与团队技能匹配现在进入横向对比。先看最基础的开发语言维度。方案开发语言渲染基础团队切入成本一句话RNJavaScript / TypeScript原生控件 桥接有 React 经验即可原生知识越多越好FlutterDart自绘引擎 Skia/ImpellerWeb 转需重新适应 Widget 树学习曲线陡UniAppVue 2/3 JS多端编译App 端常走 WebView会 Vue 基本零成本小程序WXML/WXSS JS双线程渲染层会前端一周能上手这个表格是最直观的概览但它不能决定最终选型因为团队可以通过招聘或外包来解决“谁来做”但框架原理层面的差异没办法靠换人解决。接着讲团队构成如果你手里的人全是 Web 出身想最快做出一个多端 DemoUniApp 几乎是最优解如果团队里已经有一位原生开发Flutter 的加持会非常明显因为遇到地图、支付、相机等原生能力时接插件会很快如果团队是 React 技术栈RN 能把现有前端技能直接复用但要注意 RN 的社区库质量和版本兼容问题。再提醒一点不要因为推崇主流技术栈就忽略团队熟悉度框架可以被快速学会但一个团队对框架生态的熟悉程度决定了它踩坑后爬出来的速度。3.2 长列表、连续动画和地图这种重场景的实测感受选型最忌讳只看官方 Demo。到真实项目里长列表和地图这种重场景往往才是分水岭。我在模拟项目X里专门做了一个压力测试一个带实时搜索的 500 条车辆列表用户每次输入都过滤并展示结果。RN 和 Flutter 在优化常规虚拟列表后都能做到基本流畅UniApp 在 App 端 WebView 上会出现较明显的输入延迟特别是打开键盘时小程序原生开发在连续 setData 时滚动掉帧需要配合分页和节流才能勉强流畅。为什么会有这么大差异核心还是在通信模型上。RN 即使要经过桥接列表本身仍然由原生 RecyclerView 或 UICollectionView 承载滚动不频繁触发 JSFlutter 虽是自绘但它可以直接在本地完成滚动和绘制不需要跨进程通信UniApp 和小程序的渲染层与逻辑层通信都是异步且花销大的长列表天然吃亏。地图场景差距更明显RN 和 Flutter 都有比较成熟的 map 插件UniApp 和小程序则通常使用平台自带的地图组件或 WebView 地图遇到自定义标记、轨迹动画时限制特别多。建议如果你的业务里包含高频长列表、实时滚动的图表、大型交互地图不要把“多端复用”当成唯一目标优先保证核心页面用更贴近原生的技术实现。也就是说整体用 UniApp 可以但核心交互页可以拆出来做成原生或直接嵌入原生组件别让框架变成瓶颈。3.3 热更新与动态化能力盘点动态化是一个业务方很喜欢问但技术方很纠结的模块。先弄清“热更新”在不同框架里有完全不同的含义。RN 通过 CodePush 或自建 bundle 服务可以把 JS Bundle 远程下发并加载业务逻辑的更新几乎可以不发版。但要注意iOS 平台对远程代码执行有严格限制如果被平台警告轻则不能热更重则影响上架状态。Flutter 官方没有提供热更新机制因为它默认采用 AOT 编译代码已经编成二进制要热更新就得引入动态加载框架这在主流应用商店体系中风险很高所以大多数团队不会把 Flutter 的热更当成路线图上的关键能力。UniApp 在 App 端有资源热更新能力能够通过云打包后的资源包到客户端更新和 Web 端完全不一样但平台限制同样存在。小程序最特殊它的更新不依赖用户安装开发者发布新版本到服务端后用户端会自动拉取但小程序审核仍然需要时间而且版本回滚不像 App 端那么可控。这里把四种能力做成一个速查表方案是否支持动态更新常见手段主要限制RN支持CodePush / 自建 Bundle 服务平台审核风险、兼容版本风险Flutter不支持官方无热更平台审核风险维护成本高UniAppApp 端支持资源包热更 / wgt 包平台规则、原生代码仍需发版小程序天然支持服务端发布生效平台审核、限流、版本回滚困难选型的时候别把“是否能热更”当成绝对优势因为热更越多黑盒风险越大。你可以做灰度发布、监控崩溃率、准备回滚方案但平台规则始终是灰色地带要谨慎处理。3.4 生态完善度与第三方库覆盖生态是决定项目能走多远的重要指标。RN 的最大生态是 npm地图、推送、支付、分享这些 SDK 基本都有封装但版本碎片化很严重。你搜一个库可能要面临新旧架构的兼容、原生 SDK 版本冲突甚至有些库已经两三年没更新。Flutter 的 pub 生态有很多 First-party 插件基本覆盖主流能力但冷门设备能力往往要靠自己写 Platform Channel需要同时懂 Dart 和原生。UniApp 的插件市场看似很全但插件的质量参差不齐很多是个人开发者自测不适合直接上生产。小程序呢各平台都有自己的官方组件和插件但同一个功能在不同平台上的 API 行为不一致“微信能跑、支付宝报错”的情况很典型。这里给一个实操建议在立项阶段就挑五个最重要的第三方能力比如“地图、支付、推送、埋点、文件预览”去对应方案的实际社区里查一查最近半年的 issue 和版本更新时间。如果核心那个库的维护者半年内没发布版本并且 issue 里批量出现崩溃问题建议直接换方案别拿 star 数自欺欺人。另外团队内部可以建立一份“原生能力清单”把哪些必须原生接入、哪些可以用现成插件标注出来这比单纯比较 star 数量有用得多。4. 各方案适用场景与组合策略没有银弹只有取舍4.1 电商类 App 如何选电商是跨端方案最集中的领域。原因是电商产品页面结构复杂既要快速迭代又要保证滑动体验。先说结论如果目标是做体验优先的电商 AppFlutter 是当前比较稳的选项。商品瀑布流、sku 选择弹窗、购物车浮层这些高频交互在 Flutter 里都可以通过自绘引擎保持稳定的帧率。RN 也能做但需要更精细地控制列表复用和原生交互团队如果没有原生能力会吃力一些。UniApp 能做普通电商业务但在复杂的商品详情页上很容易和 H5 活动页混在一起性能不太可控。小程序电商则是另一个战场用户更看重“打开即用”而不是华丽动效所以直接用原生小程序开发反而更合适。关键点不要把电商的重心押在框架上。电商的难点在于活动配置系统、价格库存、优惠计算、服务端渲染策略。App 端只要列表滑动不卡详情页能承载各种活动组件这个框架就算合格的。很多电商项目会同时存在 App 和小程序这时我建议不要把两端的 UI 代码强行共享而是把业务协议、字段模型、组件规范统一前端层按端分别实现。这样看起来工作量更大但可以让两端都能顺畅演进避免为了“一条代码通吃”牺牲用户体验。4.2 工具类和管理后台类 App 如何选如果你做的是内部工单、企业审批、数据报表这类工具不必追求原生级动画绝大多数场景都是“页面跳转 表格 表单 列表”。这种产品对体验要求不高但对开发效率和可维护性要求高。UniApp 或 H5 套壳可能是最经济的选择。原因很简单管理后台本质就是把数据库里的数据搬到界面上再加一堆筛选和操作按钮UniApp 能编译到 H5PC 端也能用API 还和 Vue 生态打通团队沟通成本最低。如果工具类产品里有大量图表比如仪表盘、走势图、勘测数据标注Flutter 会更合适因为它的 Canvas 绘图能力强虽然成熟的图表库没有 Web 端那么丰富但交互性能比 WebView 图表好不少。RN 在工具类里也有位置但跟 UniApp 比成本偏高除非你的团队本身是 React 技术栈。这里要做一个成本判断工具类产品通常预算有限开发周期短。与其追求最好的滚动表现不如把时间花在业务闭环和权限设计上。很多内部工具最后上线了用户觉得“能用”就够了很少有人会盯着列表滚动有没有 60 帧来验收。4.3 小程序直出和 App 混合的常见组合玩法现实世界很少只用一套方案多数产品会同时存在 App、小程序、H5 多渠道。我见过比较成功的组合是“App 用 Flutter 做核心内核小程序单独用原生或 Taro 做H5 用 Vue 或 React 做活动页”三端统一的是后端 API 和设计语言UI 层各自为战。这个思路看上去浪费了“一套代码多端复用”的机会但实际上隐藏着不少优势每端可以独立发版、独立优化不会为了其中一个平台的怪异问题拖累所有端。另一个常见组合是“RN 做纯 App 业务用 WebView 嵌入 H5 活动运营页微信侧单独维护一个小程序”因为 RN 的 WebView 集成非常成熟H5 活动页又能快速更新正好避开 App 发版周期长的问题。还有团队用 UniApp 只做轻业务端比如报名、资讯、客服等边缘页面核心页面仍然用原生或 Flutter 实现也算是一种互补。需要提醒的是混合方案对架构的要求更高。你必须有清晰的模块边界像“哪部分属于核心交易链路哪部分属于运营活动链路”这种问题在选型前就要定义清楚。如果混合之后所有模块都层层耦合后期维护比单一框架更痛苦。我倾向于把跨端框架当作“业务分发器”核心能力尽量放在原生或 Flutter 层边缘需求交给 H5 或小程序让最合适的技术处理最合适的问题。4.4 我踩过的坑和现在的推荐排序最后分享一些实际选型和运维过程里遇到的典型问题。第一个坑一开始为了省时间选择某跨端框架做复杂地图轨迹绘制原型 Demo 很顺利到真实手机上做轨迹动画就卡成 PPT最后把地图模块单独用原生插件重写前后反而多花了两周。地图、视频、画布这类强原生能力跨端框架只能保证“能用”很难保证“好用”遇到这种场景最好第一时间确认有没有成熟插件而不是自己造轮子。第二个坑RN 项目升级版本时因为一个旧的推送插件没有适配新架构导致原生编译失败。查问题花了一个下午最后只能锁定旧版本并延期升级。从那天起我在团队里定了规矩第三方依赖必须锁版本每次升级要列兼容矩阵并且先在 CI 上跑冒烟测试。第三个坑Flutter 项目在 iOS 上做动态发布功能时由于涉及远程代码执行风险被审核盯上后来去掉了动态化能力才顺利过审。不是说 Flutter 不能做动态化而是这类能力在平台规则下随时可能变成产品上线风险所以不建议把核心业务逻辑押在动态更新上。现在如果让我重新做选型我会优先排一个推荐顺序需要同时快速覆盖 App 和 H5、业务又偏表单流程的首选 UniApp。需要高体验、自定义 UI、重交互的首选 Flutter。如果团队有深厚的 React 和原生基础项目又对动态发版有强需求RN 是值得考虑的。只做微信内部生态的话不用犹豫直接原生小程序配合一套好的组件库和状态管理效率和体验都能兼顾。当然排名不是一成不变的。Flutter 在逐渐更受平台生态接纳RN 的新架构也在补性能短板UniApp 也在不断尝试原生渲染。重要的是每次立项都把业务场景和团队能力画清楚再回头看这些框架答案会自然浮现。
阅读完成 · 觉得有帮助?