本节摘要:React Native 的跨平台本质是「桥接映射」——JavaScript 负责描述界面,原生层负责真实渲染,中间靠一套通信机制传递指令。理解这层抽象,再沿版本时间线看清从旧 Bridge 到 JSI 新架构的换代,你就能解释绝大多数「同一份组件在 iOS 与 Android 上表现不同」的现象。本节是全册的地基,直接服务于 1.2 的选型判断与第 2 章的架构剖析。
「Learn Once, Write Anywhere」——这句口号常被误写成「Write Once, Run Anywhere」,而这两个字母的差别恰恰是 React Native 全部设计哲学的起点。后者的野心是消灭平台差异:一份代码处处运行,界面像素级一致;前者的姿态是承认差异:学习成本只付一遍,写的时候却要尊重每个平台的习惯。React 官方团队很早就意识到,iOS 与 Android 的用户对各自主场交互模式有肌肉记忆——iOS 的返回手势在屏幕左缘,Android 的返回键在系统导航栏。强行抹平这些差异,得到的是「哪个平台都不像」的应用。
这个立场直接决定了 RN 组件的工作方式。你写下的 <View>、<Text>、<Image> 并不是被编译或翻译成别的什么,而是在运行时被映射到各平台的原生视图:iOS 上是 UIKit 家族的视图对象,Android 上是系统 View 家族的实例。组件树是 JavaScript 描述的蓝图,真正盖房子的工人始终是原生层。把这个模型记住,本节后半段的版本演化史会变得非常好懂——所谓架构换代,换的就是「蓝图怎么送到工人手里」。
把移动跨平台方案按抽象层从厚到薄排开,能看清 RN 的位置。最厚的一层是 WebView 混合方案:整个界面跑在嵌入式浏览器里,用 HTML 与 CSS 画 UI,通过 JavaScript 桥调用少量设备能力。它开发最快,但滚动、手势、动画全部受制于 WebView 的渲染性能,复杂交互场景体验明显掉队。
最薄的一层是自绘引擎方案:应用自带一套渲染引擎与 UI 组件库,不使用系统原生控件,Flutter 是代表。它换来极高的界面一致性与流畅度,代价是包体积变大、与原生控件体系隔离,接入平台专有能力需要额外写插件。
React Native 走在中间:界面不是网页,也不自绘,而是直接复用各平台的原生视图。JavaScript 层只产出「该有什么视图、什么属性、什么布局」的指令流,原生层收到后操作真实控件。这样既保住了原生质感,又让 Web 技术栈的开发者几乎无缝上手。当然,中间路线也有中间路线的麻烦——两端的原生视图本身行为不同,映射过去的组件自然继承了这些差异,比如 <Text> 在 iOS 上的字体度量与 Android 上并不完全相同,同一行文字的换行位置可能不同。这些差异不是 bug,而是「尊重土壤」的设计决策的副产品,本册会在每个具体组件处逐一对照。
RN 的版本史,本质是那层「蓝图传递机制」的进化史。把关键节点按时间排开,能看到一条清晰的换代曲线。

早期版本靠一条异步消息桥连接两个世界:JavaScript 线程把界面变更序列化成消息发过去,原生线程收到再反序列化执行。两边互不阻塞,但代价是每条消息都要经过序列化、传输、反序列化三道工序,批量数据一多,桥就成了堵点——列表快速滚动时的掉帧,多数能追溯到这条消息通道。0.60 前后是「现代化改造期」:依赖自动链接省去了手动配置原生工程的繁琐,AndroidX 迁移解决了与 Android 官方库的兼容问题,Hermes 引擎开始作为可选项提供,专门优化移动端的启动速度与内存占用,并在 0.70 起成为双端默认。真正的换代发生在 0.76:新架构默认开启,JSI 让 JavaScript 可以持有原生对象的引用直接同步调用,消息桥从「必需品」降级为「兼容层」,Fabric 渲染器把视图更新改成同步可中断的优先级调度,TurboModules 则让原生模块按需初始化而不是启动时全量挂载。
背景是这样的:某工具类应用停留在 0.63 版本,列表页在低端 Android 机型上滚动掉帧明显,团队决定升级到新架构默认开启的版本。操作分四步:先用升级辅助工具生成两版差异清单,确认第三方依赖里有两个涉及旧桥接口的原生模块需要换用支持新架构的替代品;再处理 iOS 侧的依赖管理器切换,把 Podfile 与相关配置对齐新模板;然后在 Android 侧统一编译版本与目标版本号,跑通构建;最后在双端真机上回归核心链路,重点盯列表滚动、相册选取与推送注册三个场景。
结果符合预期也有意外。滚动帧率在低端 Android 机上改善明显,因为 Hermes 默认启用加列表渲染路径优化;意外出现在推送模块——它内部依赖的旧接口在新架构下行为不同,首轮回归没有覆盖到「应用冷启动前收到推送」的场景,线上灰度时才暴露。解读这次升级可以得出两点:新架构的红利是真实的,但集中在通信密集与启动敏感的场景;两端的原生依赖是升级风险的主体,JS 层代码几乎零改动。变式的思考是:如果团队停留在旧架构维护老应用,那么本册讲到的桥接拥堵类优化(第 6 章列表篇)依然适用,而讲到的同步直调类新特性则要等升级后才能享用。同一份教程知识,在两条技术路线上有不同的生效顺序,这正是「土壤决定长势」的又一层含义。
用一小段代码验证「组件是描述、原生是执行」这个模型。下面这个问候组件没有任何平台判断代码,却在两端都能正常渲染——因为真正决定字体渲染与触摸反馈的是各自的原生控件:
import React from 'react'; import { View, Text, Pressable, StyleSheet } from 'react-native'; export default function Greeting({ name }) { const [pressed, setPressed] = React.useState(false); return ( <View style={styles.card}> <Text style={styles.title}>你好,{name}</Text> <Pressable onPressIn={() => setPressed(true)} onPressOut={() => setPressed(false)} > <Text style={[styles.button, pressed && styles.buttonActive]}> 打个招呼 </Text> </Pressable> </View> ); } const styles = StyleSheet.create({ card: { padding: 16, backgroundColor: '#f5f7fa', borderRadius: 12 }, title: { fontSize: 18, fontWeight: '600', marginBottom: 8 }, button: { color: '#1a73e8', fontSize: 15 }, buttonActive: { opacity: 0.6 }, });
分别在双端运行后观察细节:按下按钮时,iOS 上文字透明度变化伴随 UIKit 的按压反馈曲线,Android 上则是 Material 设计规范的涟漪动效附近的行为——你的代码完全相同,土壤各自发力。这个实验价值不大,但结论价值连城:判断一个跨平台框架的好坏,先看它把「一致」与「差异」的分界线画在哪里。
下一节把 RN 放到选型桌上,与原生双轨、Flutter 正面对比——本节的原理将成为对比表格里每一格的判据。