这一章不是把
React面试题换一套组件名,而是围绕真实React Native岗位常见要求展开:能写跨端业务、理解运行原理、能和iOS/Android原生协作、能定位性能与发布问题。
# 1 基础与选型
# React Native 到底是什么,和写 React Web 差在哪?
30 秒速记
- 复用的是
React的组件 + 状态 + 协调模型,宿主换成了iOS/Android原生视图,不是DOM View→UIView/android.view.ViewGroup,Text→ 原生文本控件;裸字符串不包Text直接报错- 布局交给
C++写的Yoga算,样式是JS对象,只有RN支持的那部分属性,没有选择器和级联 - 新架构(
0.76起默认):JSI直接调C++,Fabric渲染,TurboModules按需加载,老的异步Bridge退场 - 热更新只能换
JS包和图片,加原生SDK、改权限声明必须重新发版
React Native 说白了就是 React 换了一个渲染目标:组件、state、diff 那一套照旧,但最后挂上去的是原生控件,不是浏览器 DOM。 所以 div、span、window、document 统统没有,文字必须放进 Text,样式是驼峰对象,默认还是纵向排列。从 0.76 开始新架构默认开启,JS 通过 JSI 直接和 C++ 层打交道,不再走以前那条异步序列化的 Bridge。业务逻辑、请求、状态管理基本能和 Web 共用,但权限、推送、前后台切换这些得按原生的规矩来。
# 一次更新从 setState 到屏幕
React 本身只负责算出「界面应该长什么样」,真正落地交给渲染器:Web 上是 react-dom,RN 里是 Fabric(老架构叫 Paper)。新架构下一次更新大致分三段:
- 渲染:
React执行组件拿到React Element Tree,渲染器在C++里生成对应的Shadow Tree,每个节点记录类型、props和样式。 - 提交:
Yoga对Shadow Tree算布局,得到每个节点的位置和尺寸,新树被标记为「下一棵要挂载的树」。 - 挂载:在主线程(
UI线程)上把新旧树的差异变成原生视图的增删改,比如创建一个UIView、改一个TextView的文字。
全程没有 DOM,也没有浏览器那种样式计算。老架构里 JS 线程和原生之间靠 Bridge 传 JSON 消息,全异步、要序列化,大列表快速滚动时容易丢帧;新架构的 JSI 让 JS 能直接持有 C++ 对象的引用,可以同步调用,Fabric 也因此能支持 React 18 的并发特性。版本上记几个节点就够:0.68 能手动开新架构,0.76 默认开启,0.82 起老架构已经关不回去了;引擎方面 Hermes 从 0.70 起是默认 JS 引擎。
# 写代码时最直观的差异
import { Platform, StyleSheet, Text, View } from 'react-native'
export function Badge({ count }: { count: number }) {
return (
// View 是容器,默认 flexDirection: 'column'
<View style={styles.wrap}>
{/* 文字必须包在 Text 里,写成 <View>{count}</View> 会直接报错 */}
<Text style={styles.text}>{count > 99 ? '99+' : count}</Text>
</View>
)
}
const styles = StyleSheet.create({
wrap: {
flexDirection: 'row', // 想横排要显式写
paddingHorizontal: 6, // 数字,没有 px,单位是逻辑像素
borderRadius: 9,
backgroundColor: '#f5222d',
// 平台差异用 Platform.select 收口,别在 JSX 里到处 if
...Platform.select({
ios: { shadowColor: '#000', shadowOpacity: 0.2, shadowRadius: 2, shadowOffset: { width: 0, height: 1 } },
android: { elevation: 2 },
}),
},
text: { color: '#fff', fontSize: 12, fontWeight: '600' },
})
几个关键点:StyleSheet.create 只是给样式对象做类型校验和引用复用,本质还是普通对象;Text 的样式只在嵌套的 Text 之间继承,View 上写 color 对里面的字没用;阴影在 iOS 用 shadow* 一组属性,Android 老版本只能靠 elevation,0.76 起新架构才支持统一的 boxShadow。
# 哪些能复用,哪些不能
请求封装、状态管理(zustand、Redux)、表单校验、工具函数基本可以直接搬。路由要换成 React Navigation 这类原生栈导航;存储从 localStorage 换成 AsyncStorage 或 MMKV;相机、定位、推送都要申请原生权限,还要处理 AppState 前后台切换。容易误判的是热更新:CodePush 一类方案替换的是 JS 包和静态资源,只要改动涉及原生代码、新增 SDK 或者 Info.plist / AndroidManifest.xml 里的权限,就只能走商店发版。
面试官追问
追问 1同事把 Web 的头像组件直接拷过来,里面有 <span> 和 window.innerWidth,报什么错、怎么改?
span 不是 RN 的宿主组件,会直接报找不到组件;window.innerWidth 是 undefined。换成 Text,宽度用 useWindowDimensions() 拿,它还会在横竖屏切换时自动触发重渲染。
追问 2老架构的 Bridge 到底慢在哪,JSI 解决了什么?
Bridge 是异步消息队列,每次调用都要把参数序列化成 JSON,过去再反序列化,量大时会堵。JSI 让 JS 直接持有 C++ 对象引用、同步调用,没有序列化开销,像 react-native-reanimated、MMKV 都是靠它才能做到同步读写。
追问 3升级到 0.76 之后有个老的第三方库白屏了,你先查什么?
先看这个库是不是还依赖老架构的 NativeModules / requireNativeComponent 写法。0.76 有兼容层,大部分老库能跑,但用了私有 Bridge API 的会挂;去 React Native Directory 查它的新架构支持情况,不支持就换库或者自己写 TurboModule。
追问 4RN 的 JS 跑在哪个线程,JS 卡住了页面还能滚吗?
JS 跑在单独的 JS 线程,原生 ScrollView 的滚动在 UI 线程,所以 JS 卡住时列表还能滑,只是点击没反应、新内容不渲染,FlatList 会露出白块。动画想不受 JS 影响,就用 useNativeDriver: true 或 Reanimated 的 worklet。
追问 5用户说切到后台再回来,上传进度条不动了,这是 React 的问题吗?
多半不是。iOS 进后台很快会挂起 JS 执行,普通 fetch 上传也会被系统停掉。要监听 AppState,回到前台时重新同步状态;真要后台继续传,得用原生的后台上传任务,JS 层自己做不到。
# 新项目要做 App,RN、Flutter、原生和 WebView 混合你怎么选?
30 秒速记
- 先问清楚:页面类型、原生
SDK有多少、性能硬指标、团队现有技术栈、要维护几年 RN:React团队上手快、业务页迭代快、能热更新JS;代价是原生依赖升级和双端细节差异Flutter:自绘引擎,双端像素一致;代价是Dart、包体略大、原生插件生态要自己补- 原生:性能和系统能力上限最高,音视频、
AR、重图形首选;代价是两套代码两拨人 WebView:活动页、协议页最快上线,体验和内存是短板;大应用常见「原生壳 +RN业务页 +H5活动」混搭
没有哪个方案通吃,我会先看页面类型和团队,再看性能红线。 团队是 React 背景、主要做电商、社交、工具这类常规业务,我倾向 RN,业务页能和 Web 共用不少逻辑,JS 还能热更新。要求两端视觉完全一致、动效很重,Flutter 更合适,它自己画每一个像素。音视频、AR、深度系统集成这种,老老实实写原生。运营活动页一周几个,用 WebView 最省事。真实项目里一般是混着用,关键是把边界划清楚。
# 四种方案对比
| 方案 | 渲染方式 | 优势 | 主要代价 | 适合 |
|---|---|---|---|---|
RN |
映射到原生控件 | React 生态、业务迭代快、JS 可热更新 |
双端差异、原生依赖升级、复杂动画和长列表要调 | 电商、内容、社交、工具类 |
Flutter |
自绘(Skia,新版本是 Impeller) |
双端 UI 一致、动画性能稳 |
Dart 技术栈、包体偏大、平台控件要靠嵌入 |
强品牌视觉、多端统一 UI |
| 原生 | 系统控件 | 性能和系统能力最完整 | 两套代码、人力成本最高 | 音视频、AR |
