📱 React Native 与移动跨端
一句话定性
移动跨端是前端的第一次远征。前端盯着 iOS/Android 这两块原生疆域,只有一个执念:别让我用两种语言、两支团队、写两遍同一个 App。 从 WebView 套壳到 React Native 的”JS 写原生组件”,这部历史就是不断在”复用 Web 技能”和”接近原生体验”之间寻找停火线——而 Flutter 干脆掀了桌子,告诉所有人:也许根本不该复用 Web。
一、它是什么 & 出现的时代
智能手机普及后,前端面对一个尴尬现实:用户的注意力从浏览器迁移到了原生 App,而原生 App 要用 Objective-C/Swift(iOS)和 Java/Kotlin(Android)分别写两遍。 对一支只会 HTML/CSS/JS 的前端团队来说,这是两座必须翻越的高山。
于是移动跨端按时间分成三代:
| 代际 | 代表 | 年代 | 渲染方式 | 一句话 |
|---|---|---|---|---|
| 第一代:WebView 套壳 | Cordova / PhoneGap / Ionic | 2009 起 | 整个 App 是一个全屏 WebView | ”把网页装进 App 壳里” |
| 第二代:JS 驱动原生 | React Native | 2015 | JS 逻辑 + 真正的原生组件 | ”JS 写,但渲染交给原生” |
| (参照系)自绘引擎 | Flutter(非 JS,Dart) | 2017 | 自带 Skia 引擎,每个像素自己画 | ”谁的组件都不用,我自己画” |
这是 跨端与全栈演进史 横向扩张主线的第一站
移动端是前端”领土扩张”踏出浏览器的第一步。它定下的张力——复用 vs. 原生——会在后面的桌面端、小程序里一再重演。
二、为什么会出现(解决上一代什么痛点)
原生双端开发的"两遍地狱"
第一代(Cordova/PhoneGap,2009)的回答最简单粗暴:既然 App 能装一个 WebView,那我把整个网页塞进去不就行了? 一套 HTML/CSS/JS,通过 Cordova 提供的 JS bridge 调用摄像头、GPS 等原生能力。Ionic 在此基础上提供了一套”看起来像原生”的 UI 组件库。
这解决了”复用 Web 技能”的诉求,但很快撞上天花板。
三、核心机制 & 为什么流行
第一代的崩塌,催生 React Native
WebView 套壳的致命伤是:它本质上还是一个网页,所有交互都隔着 WebView 这层玻璃。 滚动卡顿、动画掉帧、列表长了就崩——用户一眼就能看出”这不是原生 App”。“uncanny valley(恐怖谷)“式的体验,让 Cordova 系沦为对体验不敏感的内部工具/简单应用的选择。
2015 年,Facebook 开源 React Native,提出了一个关键升级:
React Native 的核心洞察:"Learn once, write anywhere"
别再用 WebView 渲染了。JS 只负责描述 UI(用 React 的组件心智),但真正渲染出来的,是平台的原生组件——
<View>在 iOS 上变成UIView,在 Android 上变成android.view。// 这不是 HTML,<View>/<Text> 会被映射成真正的原生组件 function Card() { return ( <View style={styles.card}> <Text>Hello Native</Text> </View> ); }注意它的口号不是 React 的 “write once, run anywhere”,而是 “learn once, write anywhere”——承认两端有差异,但心智和技能可以复用。这是对”复用 vs. 原生”光谱的一次精妙折中。
架构:JS 线程 + 原生线程 + Bridge。 JS 在独立线程跑业务逻辑,通过一座异步的 Bridge(后来演进为 JSI/Fabric 的同步调用)与原生线程通信,原生线程负责真正的渲染和手势。逻辑用 JS 写(可复用前端技能),渲染用原生(保证体验)。
为什么流行
- 技能复用兑现了:会 React 的人几乎能直接写 RN,组件、props、state、Hooks 全部沿用。前端团队第一次真正能做 App。
- Facebook/Instagram 背书 + 热更新(CodePush) 绕开了应用商店审核的痛点。
- 生态借力 npm:海量 JS 库可复用。
Flutter:另一条路(重要参照系)
2017 年 Google 推出 Flutter(用 Dart 语言,不属于 JS 系)。它的选择是光谱的另一个极端:既不套 WebView,也不用原生组件,而是自带 Skia 渲染引擎,每个像素都自己画。 好处是两端像素级一致、性能接近原生;代价是完全不复用 Web 技能(要学 Dart),且无法直接用原生组件。
为什么要把 Flutter 放进这部 JS 主导的历史?
因为 Flutter 是对整条母题的反命题。当前端的扩张逻辑是”用 JS 复用 Web 技能征服一切”时,Flutter 站出来说:“复用 Web 技能”本身可能就是错的约束——如果体验才是第一目标,那就别复用,从渲染层重做。 这是第一性原理对”从众假设”的一次正面挑战。RN 和 Flutter 之争,本质是”技能复用优先” vs. “体验一致优先”两种价值观之争。
四、带来的新问题 / 副作用
"JS 写原生"的抽象泄漏
- Bridge 性能瓶颈(早期):JS 与原生通过异步 Bridge 序列化通信,高频场景(复杂动画、大列表快速滚动)会卡顿。这逼出了 RN 后来的 JSI / Fabric / TurboModules 重构(把异步 Bridge 换成同步直调)。
- 原生模块维护的隐性成本:一旦要用 RN 没封装的能力(蓝牙、特定 SDK),还是得写原生代码 + 桥接层。号称”不用碰原生”,最后往往还是要懂 iOS/Android。 抽象泄漏了。
- 版本碎片与升级地狱:RN 版本、各原生依赖、Gradle/CocoaPods、第三方库的兼容矩阵极其脆弱,“升级 RN 大版本”在团队里是出了名的苦差事。
- “差不多但不完全一样”:两端总有细微差异要单独处理,理想的”一套代码”在边角处不断渗水。
第一性原理的拷问:跨端省下的”写两遍”的钱,有多少被”调两端差异 + 维护原生桥接 + 追 RN 版本”重新花掉了? 这是每个团队选型时必须自问的账。
五、为什么会衰落 / 现状
React Native 没有衰落,而是进入了成熟期并完成了关键自我进化:
RN 的自我革命
现状格局:
- React Native:JS 生态、可复用前端团队的首选,大量大厂 App 在用。
- Flutter:对 UI 一致性和性能要求极高、团队愿意学 Dart 的场景里强势。
- Cordova/Ionic:基本退居简单应用 / 企业内部应用,被 RN 和 Flutter 两面挤压。
六、对后续技术的影响(因果链)
原生双端开发"写两遍"的痛点
│
↓
第一代:Cordova/PhoneGap(2009)── 整个 App 套一个 WebView
│ └─ Ionic:在套壳上加"类原生"UI 组件库
│
├─► 致命伤:隔着 WebView,体验掉进恐怖谷
│
↓
React Native(2015):JS 写逻辑 + 原生组件渲染 + Bridge
│ 口号从 "write once" 降级为 "learn once"(承认差异)
│
├─► Bridge 性能瓶颈 ──► JSI/Fabric/TurboModules 重构
├─► 配置复杂 ──► Expo 成为标准入口
├─► 文件路由 ──► Expo Router ──► 并入全栈元框架版图 [[跨端与全栈演进史]]
│
‖ (反命题)
↓
Flutter(2017,Dart 非 JS):自绘引擎,不复用 Web,但体验一致
│ 挑战了"复用 Web 技能"这个前提本身
↓
移动跨端定型:RN(技能复用派) vs Flutter(体验一致派)长期共存
历史地位
React Native 是前端”领土扩张”的第一面旗帜,它证明了前端的组件心智可以脱离浏览器、驱动原生 UI。它定下的”复用 vs. 原生”光谱,成为之后整部跨端史的坐标系。而 Flutter 的存在则永远提醒着:“用一套 JS 技能统一一切”是一种选择,不是一条物理定律。
🔗 同组:跨端与全栈演进史 | 小程序生态 | Electron与桌面端 | 全栈框架与BFF | RSC与前后端边界模糊 🔗 框架:React | Vue | 前端框架演进史 🔗 时代:2013-2018 SPA时代 | 2018-2023 工程化时代 🔗 运行时:Node.js | 语言:TypeScript