React Native 八股文 · 全面深入指南
涵盖底层原理、第三方库、难点问题、性能优化、横向对比、高难度深底层原理
目录
第一部分:最全面最底层的八股文
1.1 React Native 整体架构演进
1.1.1 旧架构(Bridge 架构)
React Native 旧架构的核心是 Bridge(桥接),整体分为三层:
- JS 线程(JS Thread):运行 JavaScript 代码,包含业务逻辑、React 组件树的 diff 计算。JS 引擎在 iOS 上默认使用 JavaScriptCore(JSC),Android 上可选 JSC 或 Hermes。
- Shadow 线程(Shadow Thread):运行 Yoga 布局引擎,负责将 Flexbox 布局声明转换为具体的像素坐标。Shadow Thread 维护一棵 Shadow Tree,是 JS 端虚拟 DOM 到原生视图之间的中间表示。
- 主线程(Main/UI Thread):负责原生视图的创建、更新、渲染,处理触摸事件等。
Bridge 的通信机制:
- JS 线程将操作序列化为 JSON 消息,放入消息队列
- Bridge 将消息从 JS 线程异步传递到 Native 线程
- Native 线程反序列化 JSON,执行对应的原生操作
- 返回结果同样经过序列化 → Bridge → 反序列化的过程
Bridge 的瓶颈:
- 序列化/反序列化开销:所有数据都要经过 JSON 序列化,大数据量时性能急剧下降
- 异步通信延迟:Bridge 通信是异步批量的,不能实时响应,滑动列表时容易出现白屏
- 单线程瓶颈:JS 线程和 UI 线程之间通过单一 Bridge 通信,存在排队等待
- 无法同步调用:JS 无法同步获取原生数据(如布局尺寸),必须回调
1.1.2 新架构(Fabric + TurboModules + JSI + Codegen)
JSI(JavaScript Interface):
- JSI 是一个轻量级的 C++ 抽象层,允许 JS 直接持有 C++ 对象的引用(Host Objects)
- JS 可以直接调用 C++ 方法,无需序列化/反序列化
- JSI 与 JS 引擎解耦,可以替换底层引擎(JSC → Hermes → V8)
- 通过 JSI,JS 可以同步调用 Native 方法,也可以异步调用
TurboModules:
- 替代旧的 NativeModules 系统
- 基于 JSI 实现,JS 可以直接持有 Native Module 的引用
- 支持懒加载:模块在首次访问时才初始化,而非应用启动时全部初始化
- 支持同步调用和异步调用
- 通过 Codegen 自动生成类型安全的接口代码
Fabric 渲染器:
- 替代旧的 UI Manager
- 基于 JSI 实现,JS 端可以直接操作 Shadow Tree
- 支持同步渲染和异步渲染(Concurrent Mode 兼容)
- 渲染流水线:Render → Commit → Mount
- Render 阶段:在 JS 线程创建 React Element Tree,生成 React Shadow Tree(不可变的 C++ 对象)
- Commit 阶段:在后台线程进行布局计算(Yoga),提升 Shadow Tree,执行 Tree Diffing
- Mount 阶段:在主线程将 Shadow Tree 差异应用到 Host View Tree(原生视图树)
- Shadow Tree 是不可变的(Immutable),每次更新创建新树,通过 diff 算法计算差异
Codegen:
- 编译时工具,根据 Flow/TypeScript 类型定义自动生成 C++、Java、ObjC 的胶水代码
- 保证 JS 和 Native 之间的类型安全
- 减少手写 Bridge 代码的错误
1.1.3 Bridgeless 模式
- 新架构的最终形态是完全移除 Bridge
- 所有通信通过 JSI 直接进行
- 旧的 NativeModules 通过互操作层(Interop Layer)桥接到新架构
- 实现了真正的 JS ↔ Native 直接通信
1.2 JavaScript 引擎
1.2.1 JavaScriptCore (JSC)
- WebKit 项目的一部分,iOS 上默认引擎
- iOS 上使用系统自带的 JSC framework,不可定制
- Android 上使用打包在 APK 中的 JSC,可以替换
- 不支持 JIT(Just-In-Time)编译优化(iOS 限制)
- 内存占用相对较高
1.2.2 Hermes
- Meta 专门为 React Native 优化的 JS 引擎
- AOT 编译:将 JS 源码预编译为 Hermes 字节码(.hbc),应用运行时直接加载字节码
- 优势:
- 更快的启动时间:无需运行时解析和编译 JS
- 更低的内存占用:字节码比 AST 占用更少内存
- 更小的包体积:字节码通常比压缩后的 JS 源码更小
- 更好的垃圾回收:使用分代 GC + 增量标记 + 惰性扫描
- Hermes 的 GC 策略:
- GenGC(分代垃圾回收):分为 Young Gen 和 Old Gen
- Young Gen 使用 semi-space copying,Old Gen 使用 mark-compact
- 支持增量 GC,避免长时间暂停
- Hermes 不支持的 JS 特性:
with语句、eval()的动态作用域、部分 Proxy 功能 - RN 0.70+ 默认在 iOS 和 Android 上启用 Hermes
1.2.3 V8(非官方)
- 通过 react-native-v8 等第三方库可在 Android 上使用
- 支持 JIT 编译,运行时性能最高
- 但启动时间较长,内存占用最高
1.3 React 核心原理(RN 上下文)
1.3.1 虚拟 DOM 与 Reconciliation
- React 维护一棵虚拟 DOM 树(React Element Tree)
- 当 state/props 变化时,React 创建新的虚拟 DOM 树
- 通过 Reconciliation(协调) 算法对比新旧两棵树,找出差异
- 将差异批量应用到宿主平台(在 RN 中是原生视图)
Reconciliation 核心策略:
- 同层比较:只比较同一层级的节点,不跨层级比较(O(n) 复杂度)
- 类型判断:如果节点类型不同,直接销毁旧节点及其子树,创建新节点
- Key 机制:通过 key 标识列表中的元素,优化列表的增/删/移操作
- 组件类型:同类型组件复用实例,不同类型组件重新创建
1.3.2 Fiber 架构
- React 16+ 引入的新协调引擎
- 每个 React Element 对应一个 Fiber 节点
- Fiber 节点是一个链表结构:
child、sibling、return(父节点) - 核心特性:
- 可中断渲染:将渲染工作拆分为多个小单元(unit of work)
- 优先级调度:不同更新有不同优先级(用户交互 > 数据请求 > 过渡动画)
- 双缓冲:维护 current tree 和 workInProgress tree,更新完成后交换
Fiber 节点的关键属性:
tag:组件类型标识(FunctionComponent, ClassComponent, HostComponent 等)stateNode:指向实际 DOM/Native 节点或类组件实例pendingProps、memoizedProps:新旧 propsmemoizedState:当前 stateupdateQueue:待处理的更新队列effectTag/flags:标记需要执行的副作用(Placement, Update, Deletion)lanes:优先级标记(React 18+)
Fiber 工作流程(两阶段):
-
Render Phase(可中断):
- 从根节点开始,深度优先遍历 Fiber 树
- 对每个 Fiber 节点执行
beginWork(向下遍历)和completeWork(向上返回) - 收集需要执行的副作用(effect list)
- 此阶段纯计算,不产生可见的 UI 变化
-
Commit Phase(不可中断):
- Before Mutation:执行
getSnapshotBeforeUpdate - Mutation:执行实际的 DOM/Native 操作(增/删/改)
- Layout:执行
componentDidMount/componentDidUpdate/useLayoutEffect
- Before Mutation:执行
1.3.3 Hooks 底层实现
useState 底层:
- 每个 Hook 在 Fiber 节点上对应一个 Hook 对象,形成链表
- Hook 对象结构:
{ memoizedState, baseState, baseQueue, queue, next } queue中存放dispatch产生的更新(Update 对象链表)- 更新时,遍历 queue 中的 Update,依次计算新 state
- 使用
Object.is进行浅比较,如果新旧 state 相同则跳过重渲染(bailout)
useEffect 底层:
- Effect 存储在 Fiber 节点的
updateQueue中 - 每个 Effect 对象:
{ tag, create, destroy, deps, next } create:effect 回调函数destroy:上一次 effect 返回的清理函数deps:依赖数组- 执行时机:在 Commit Phase 之后,异步调度执行(通过
MessageChannel或setTimeout) - 对比
useLayoutEffect:在 Commit Phase 的 Layout 子阶段同步执行
useRef 底层:
- 创建一个
{ current: initialValue }的普通对象 - 在组件的整个生命周期内保持同一个引用
- 不会触发重渲染
useMemo / useCallback 底层:
- 在 Fiber 节点的 Hook 链表中存储
[value, deps]或[callback, deps] - 每次渲染时对比 deps(使用
Object.is逐项比较) - deps 不变则返回缓存值,否则重新计算
useContext 底层:
- 直接读取最近的 Provider 的 value
- 当 Provider value 变化时,所有消费该 Context 的组件都会重渲染
- 不受
React.memo、shouldComponentUpdate等优化手段的影响 - 这是因为 Context 变化会直接标记消费组件的 Fiber 需要更新
1.3.4 React 18+ Concurrent Features 在 RN 中的应用
- startTransition:将状态更新标记为低优先级,避免阻塞用户交互
- useDeferredValue:延迟更新值,让紧急更新先完成
- Suspense:组件可以 "暂停" 渲染,等待异步数据
- 在 RN 中,Concurrent Mode 需要 Fabric 渲染器支持
- Fabric 的 C++ 层支持并发操作 Shadow Tree
1.4 Yoga 布局引擎
1.4.1 核心原理
- Yoga 是 Meta 开源的跨平台 Flexbox 布局引擎,用 C/C++ 编写
- 实现了 W3C Flexbox 规范的子集
- 输入:节点树 + 样式属性(flex, width, height, margin, padding 等)
- 输出:每个节点的绝对坐标和尺寸(x, y, width, height)
布局计算流程:
- 从根节点开始,递归遍历节点树
- 对每个节点执行两次 pass:
- Measure Pass:确定每个节点的固有尺寸(如文本节点需要原生测量)
- Layout Pass:根据 Flexbox 规则计算最终布局
- 计算完成后,将结果传递给原生层进行渲染
1.4.2 RN 样式系统
- RN 使用 JavaScript 对象描述样式,不支持 CSS
- StyleSheet.create() 的作用:
- 在旧架构中:将样式对象注册到 Native,减少 Bridge 传输
- 在新架构中:主要作用是代码层面的优化(对象引用缓存)
- 提供开发时的样式验证
- RN 的样式属性名使用驼峰命名(camelCase),对应 CSS 的 kebab-case
1.4.3 RN 中不支持的 CSS 特性
- 不支持 CSS Grid(需要第三方库)
- 不支持 CSS 选择器、伪类、伪元素
- 不支持 CSS 变量
- 不支持
display: inline(所有元素默认display: flex) - 不支持
float - 不支持
overflow: scroll(需要使用 ScrollView) - 不支持百分比 padding/margin 参照父元素宽度(Yoga 限制)
1.5 线程模型
1.5.1 旧架构线程模型
| 线程 | 职责 |
|---|---|
| JS Thread | 执行 JavaScript 代码、React 渲染逻辑、业务逻辑 |
| Main/UI Thread | 原生视图操作、触摸事件处理、系统回调 |
| Shadow Thread | Yoga 布局计算 |
| Native Modules Thread | 部分 Native Module 的异步操作 |
1.5.2 新架构线程模型
- Fabric 允许在任意线程执行渲染操作
- 布局计算可以在后台线程异步进行
- 支持同步渲染路径(用于紧急更新)
- JS 线程可以同步读取布局信息(通过 JSI)
1.6 事件系统
1.6.1 旧架构事件流
- 用户触摸屏幕 → 原生系统捕获事件
- 原生事件处理器将事件序列化为 JSON
- 通过 Bridge 发送到 JS 线程
- JS 线程中 React 的合成事件系统处理事件
- 如果需要更新 UI,JS 发送更新指令通过 Bridge 返回
- 原生线程执行 UI 更新
问题:由于 Bridge 异步通信,事件处理存在延迟,导致:
- 手势响应不及时
- 滑动列表时的白屏问题
- 动画卡顿
1.6.2 新架构事件流
- 通过 JSI 直接传递事件,无序列化开销
- 支持同步事件处理
- 可以在 C++ 层直接拦截和处理部分事件
1.6.3 手势响应系统
PanResponder / Gesture Responder System:
- React Native 内置的手势系统
- 基于 "协商" 机制:多个组件可以竞争成为事件的 "responder"
- 生命周期:
onStartShouldSetResponder→onMoveShouldSetResponder→onResponderGrant→onResponderMove→onResponderRelease - 问题:全部在 JS 线程处理,手势复杂时性能差
react-native-gesture-handler:
- 手势处理完全在原生线程执行
- 支持手势组合、手势竞争
- 与 react-native-reanimated 配合实现高性能手势动画
1.7 导航系统(Navigation)
1.7.1 React Navigation
- 纯 JS 实现的导航库
- 使用 React 状态管理导航状态
- 页面切换动画可以通过 react-native-reanimated 在 UI 线程执行
- 支持 Stack、Tab、Drawer 等导航模式
- 导航状态可序列化,支持 Deep Link 和状态持久化
1.7.2 react-native-screens
- 使用原生的页面容器(iOS: UIViewController, Android: Fragment)
- 减少内存占用(被遮挡的页面可以被系统回收)
- 与 React Navigation 配合使用
1.8 热更新原理
1.8.1 CodePush 原理
- 将 JS Bundle 和资源文件上传到 CodePush 服务器
- 应用启动时检查更新
- 下载新的 JS Bundle(差量更新,使用 diff 算法)
- 应用新的 Bundle:
- 立即重启:
InstallMode.IMMEDIATE - 下次启动:
InstallMode.ON_NEXT_RESTART - 后台恢复:
InstallMode.ON_NEXT_RESUME
- 立即重启:
- 回滚机制:如果新 Bundle 导致 crash,自动回滚到上一版本
1.8.2 热更新的限制
- 只能更新 JS Bundle 和资源文件
- 不能更新原生代码(Objective-C/Swift/Java/Kotlin)
- 不能添加或修改原生模块
- 不能修改 App 的权限配置
1.9 原生模块(Native Modules)
1.9.1 旧架构 NativeModules
iOS (Objective-C):
- 创建类实现
RCTBridgeModule协议 - 使用
RCT_EXPORT_MODULE()宏注册模块 - 使用
RCT_EXPORT_METHOD()宏导出方法 - JS 端通过
NativeModules.ModuleName访问
数据传递:
- 基本类型:number, string, boolean
- 复杂类型:array, object(经过 JSON 序列化)
- 回调:
RCTResponseSenderBlock - Promise:
RCTPromiseResolveBlock+RCTPromiseRejectBlock
1.9.2 新架构 TurboModules
- 使用 Flow/TypeScript 类型定义接口(Spec 文件)
- Codegen 自动生成 C++、ObjC、Java 胶水代码
- 基于 JSI,JS 直接持有 Native 对象引用
- 支持同步方法调用
- 懒加载:首次访问时才创建实例
1.9.3 原生 UI 组件
旧架构(ViewManager):
- iOS: 继承
RCTViewManager,实现-(UIView *)view方法 - 使用
RCT_EXPORT_VIEW_PROPERTY导出属性 - JS 端通过
requireNativeComponent注册
新架构(Fabric Component):
- 定义 TypeScript Spec(组件接口)
- Codegen 生成 C++ Shadow Node 和 Component Descriptor
- 实现原生视图和 Shadow Node
- 直接通过 JSI 通信,无 Bridge 开销
1.10 Metro Bundler
1.10.1 打包流程
- Resolution(解析):从入口文件开始,递归解析所有
import/require依赖 - Transformation(转换):使用 Babel 将 JSX/TS/Flow 转换为标准 JS
- Serialization(序列化):将所有模块打包成单一 Bundle 文件
1.10.2 模块系统
- Metro 将每个模块包装在一个工厂函数中
- 使用数字 ID 标识模块(而非路径字符串,减少包体积)
__r()函数用于 require 模块__d()函数用于定义模块
1.10.3 RAM Bundle(Random Access Module Bundle)
- 将 Bundle 拆分为多个模块
- 应用启动时只加载必要的模块
- 其他模块在首次 require 时按需加载
- iOS 使用 Indexed RAM Bundle 格式
- Android 使用 File RAM Bundle 格式(每个模块一个文件)
1.11 组件生命周期
1.11.1 类组件生命周期
挂载阶段:
constructor()→static getDerivedStateFromProps()→render()→componentDidMount()
更新阶段:
static getDerivedStateFromProps()→shouldComponentUpdate()→render()→getSnapshotBeforeUpdate()→componentDidUpdate()
卸载阶段:
componentWillUnmount()
错误处理:
static getDerivedStateFromError()→componentDidCatch()
1.11.2 函数组件 Hooks 对应关系
| 类组件生命周期 | Hooks 等价 |
|---|---|
| constructor | useState 初始值 / useRef |
| getDerivedStateFromProps | 在渲染期间根据 props 更新 state |
| shouldComponentUpdate | React.memo |
| render | 函数组件本身 |
| componentDidMount | useEffect(() => {}, []) |
| componentDidUpdate | useEffect(() => {}, [deps]) |
| componentWillUnmount | useEffect 的返回清理函数 |
| componentDidCatch | 无直接对应(需 Error Boundary 类组件) |
| getSnapshotBeforeUpdate | 无直接对应 |
1.12 状态管理
1.12.1 React 内置状态管理
useState:
- 组件级别的状态
- 触发组件及其子组件的重渲染
- 更新是异步批量的(React 18 自动批处理所有更新)
useReducer:
- 适合复杂状态逻辑
- 类似 Redux 的 dispatch/action 模式
- 与 useContext 配合可实现简易全局状态管理
Context API:
- 跨组件层级传递数据
- Provider/Consumer 模式
- 性能问题:value 变化会导致所有消费者重渲染,无法精确更新
1.12.2 外部状态管理方案
Redux:
- 单一数据源(Single Store)
- 纯函数 Reducer
- 通过 dispatch(action) 更新状态
- 中间件系统(redux-thunk, redux-saga)
- 适用于大型应用
MobX:
- 基于可观察的响应式编程
observable→computed→reaction的数据流- 自动追踪依赖,精确更新
- 代码更简洁,但调试较困难
Zustand:
- 极简的状态管理库
- 基于发布-订阅模式
- 通过 selector 实现精确订阅(避免不必要的重渲染)
- 不依赖 Context,避免 Provider 嵌套
Jotai:
- 原子化的状态管理
- 每个 atom 是最小状态单元
- 自底向上的状态组合
- 基于 WeakMap 存储,自动垃圾回收
Recoil(Meta 出品):
- atom + selector 模式
- 支持异步 selector
- 与 React Concurrent Mode 深度集成
第二部分:第三方常用库原理与八股文
2.1 react-native-reanimated
2.1.1 核心原理
- 动画逻辑完全在 UI 线程 执行,不经过 JS 线程
- 使用 worklets(小型 JS 函数)在 UI 线程运行
- Babel 插件在编译时将 worklet 函数提取出来,序列化后传递给原生端
- 原生端使用独立的 JS 运行时(基于 JSI)执行 worklet
- 通过 Shared Values 在 JS 线程和 UI 线程之间共享数据
2.1.2 Shared Values
useSharedValue()创建一个可以在 JS 线程和 UI 线程之间共享的值- 修改
.value时,变化会同步到 UI 线程 - 不会触发 React 重渲染
- 底层通过 JSI 的 Host Object 实现,两个线程持有同一个 C++ 对象的引用
2.1.3 Worklets
- 使用
'worklet'标记的函数会在编译时被提取 - Babel 插件将 worklet 函数及其闭包变量序列化
- 运行时在 UI 线程的独立 JS 上下文中执行
- 通过
runOnJS()可以从 worklet 中回调 JS 线程的函数 - 通过
runOnUI()可以从 JS 线程调用 UI 线程的 worklet
2.1.4 动画 API
withTiming()/withSpring()/withDecay():基础动画useAnimatedStyle():根据 Shared Value 生成动画样式(在 UI 线程执行)useAnimatedGestureHandler():与 gesture-handler 配合,在 UI 线程处理手势Layout Animations:进入/退出/布局变化动画
2.1.5 与 Animated API 的本质区别
| 特性 | Animated(内置) | Reanimated 2/3 |
|---|---|---|
| 动画执行线程 | JS 线程(useNativeDriver=true 时部分在 UI 线程) | 完全在 UI 线程 |
| 手势联动 | 受限 | 完美支持 |
| 条件动画 | 不支持(声明式) | 支持 worklet 中的逻辑判断 |
| 基于其他动画的动画 | 受限 | 自由组合 |
| 性能 | 中等 | 最优 |
| 学习成本 | 低 | 中高 |
2.2 react-native-gesture-handler
2.2.1 核心原理
- 将手势处理从 JS 线程移到原生线程
- iOS 使用
UIGestureRecognizer - Android 使用自定义的手势处理系统
- 支持手势的声明式组合和竞争
2.2.2 手势类型
Tap:点击手势Pan:拖动手势Pinch:缩放手势Rotation:旋转手势Fling:快速滑动手势LongPress:长按手势NativeViewGestureHandler:桥接原生手势
2.2.3 手势竞争与组合
Race:多个手势竞争,第一个激活的获胜Simultaneous:多个手势同时识别Exclusive:互斥手势
2.2.4 与 Reanimated 配合
- Gesture Handler v2 的
useAnimatedGestureHandler直接在 UI 线程回调 - 手势数据(translation, velocity 等)直接在 UI 线程更新 Shared Values
- 实现 60fps 手势驱动动画
2.3 React Navigation
2.3.1 架构设计
- NavigationContainer:根组件,管理导航状态
- Navigator:导航器(Stack, Tab, Drawer),定义导航模式
- Screen:路由配置
2.3.2 状态管理
- 导航状态是一棵 JSON 树
- 每个 Navigator 管理自己的子路由状态
- 状态变化通过
dispatch(action)驱动 - 支持状态持久化(
onStateChange+initialState)
2.3.3 页面生命周期
useFocusEffect:页面获得/失去焦点时执行(类似页面可见性变化)useIsFocused:当前页面是否聚焦- 注意:Stack Navigator 中非当前页面不会卸载,仍然保持在内存中
2.3.4 Deep Linking
- 将 URL 路径映射到路由配置
- 支持 Universal Links (iOS) 和 App Links (Android)
linking配置定义 URL 到路由的映射规则
2.4 Redux & 中间件
2.4.1 Redux 核心流程
Action → Dispatch → Middleware → Reducer → Store → UI
2.4.2 Redux Thunk 原理
- 最简单的异步中间件(只有 14 行代码)
- 核心逻辑:如果 dispatch 的 action 是函数,就执行该函数并传入 dispatch 和 getState
- 适合简单的异步场景
2.4.3 Redux Saga 原理
- 基于 ES6 Generator 实现
- Saga 是一个 Generator 函数,
yield出 Effect 描述对象 - Saga 中间件解释 Effect 并执行相应操作
- 常用 Effects:
call(调用异步函数)、put(dispatch action)、take(监听 action)、fork(非阻塞调用)、select(获取 state) - 优势:可测试性强、复杂异步流程管理、取消任务
- 劣势:学习成本高、代码量大
2.4.4 Redux Toolkit (RTK)
- 官方推荐的 Redux 使用方式
createSlice:自动生成 action creators 和 reducercreateAsyncThunk:标准化异步操作- 内置 Immer(可以直接 "修改" state,Immer 内部通过 Proxy 实现不可变更新)
- RTK Query:内置的数据获取和缓存方案
2.5 网络请求库
2.5.1 Axios 在 RN 中的使用
- 底层在 RN 中使用 XMLHttpRequest polyfill
- 支持请求/响应拦截器
- 自动 JSON 序列化/反序列化
- 支持取消请求(AbortController / CancelToken)
2.5.2 React Query / TanStack Query
- 服务端状态管理库
- 核心概念:Query、Mutation、Query Invalidation
- 自动缓存、后台刷新、过期重新获取
- 支持乐观更新
- 与 RN 配合:通过
focusManager和onlineManager适配 RN 的生命周期
2.6 存储库
2.6.1 AsyncStorage
- 简单的 key-value 存储
- iOS 底层使用 serialized dictionary / SQLite
- Android 底层使用 SQLite / SharedPreferences
- 异步 API(基于 Promise)
- 不适合大量数据存储(性能差)
2.6.2 MMKV(react-native-mmkv)
- 微信开发的高性能 key-value 存储
- 基于 mmap 内存映射文件
- 同步 API(通过 JSI 直接调用 C++ 方法)
- 比 AsyncStorage 快 30x
- 支持加密存储
- 支持多进程访问
2.6.3 WatermelonDB
- 基于 SQLite 的响应式数据库
- 懒加载:只在需要时从数据库加载数据
- 观察者模式:数据变化自动触发 UI 更新
- 支持同步到远程服务器
- 适合大量结构化数据
2.6.4 Realm
- 对象数据库(非关系型)
- 零拷贝架构:直接操作 mmap 映射的数据
- 支持实时查询
- 支持跨平台数据同步(MongoDB Realm Sync)
2.7 react-native-svg
- 使用原生 SVG 渲染(iOS: CoreGraphics, Android: Android Canvas)
- 通过 ViewManager 将 SVG 元素映射到原生视图
- 支持动画(配合 Reanimated)
- 支持渐变、阴影、裁剪路径等
2.8 react-native-fast-image
- 替代 RN 内置 Image 组件
- iOS 底层使用 SDWebImage
- Android 底层使用 Glide
- 支持多级缓存(内存 → 磁盘)
- 支持优先级加载
- 支持渐进式加载
- 支持预加载
2.9 react-native-webview
- 封装原生 WebView(iOS: WKWebView, Android: Android WebView / Chromium)
- JS ↔ WebView 通信:
- RN → WebView:
injectJavaScript()/postMessage() - WebView → RN:
window.ReactNativeWebView.postMessage()
- RN → WebView:
- 支持离线加载本地 HTML
- 安全考虑:可以控制 JS 注入、cookie、导航策略
2.10 Expo
2.10.1 Expo 架构
- Expo Go:开发期间的容器 App,内置常用原生模块
- Expo SDK:一系列预构建的原生模块(Camera, FileSystem, Location 等)
- EAS (Expo Application Services):云端构建、提交、更新服务
- Expo Modules API:编写自定义原生模块的现代 API(基于 Swift/Kotlin DSL)
- Expo Router:基于文件系统的路由(类似 Next.js)
2.10.2 Managed vs Bare Workflow
| 特性 | Managed | Bare |
|---|---|---|
| 原生代码访问 | 不可直接访问 | 完全控制 |
| 构建 | EAS Build | 本地 Xcode/Android Studio |
| 原生模块 | 仅 Expo SDK | 任意第三方库 |
| 配置 | app.json/app.config.js | 原生项目配置 |
| Eject | 可以 prebuild | N/A |
第三部分:RN 开发难点与解决方案
3.1 长列表性能问题
3.1.1 问题描述
- FlatList 在数据量大时滑动卡顿、掉帧
- 快速滑动出现白屏/空白区域
- 内存持续增长,大量列表项导致 OOM
3.1.2 原因分析
- FlatList 的回收机制是 JS 层面的(而非原生的 RecyclerView/UICollectionView)
- 每次渲染新的列表项都需要经过 JS 线程 → Bridge/JSI → Native 的完整流程
- 复杂列表项的 React 组件 diffing 开销大
- 图片加载、数据处理等操作堆积在 JS 线程
3.1.3 解决方案
FlatList 优化:
getItemLayout:提供固定高度,跳过动态测量(避免异步布局计算)removeClippedSubviews={true}:移除可视区域外的子视图(iOS 效果好,Android 可能有问题)maxToRenderPerBatch:控制每批渲染的数量(默认 10,减少可降低单帧渲染压力)windowSize:控制渲染窗口大小(默认 21,即可视区域上下各 10 屏。减小可降低内存,但增加白屏风险)updateCellsBatchingPeriod:批量更新的间隔时间initialNumToRender:首屏渲染数量- 避免匿名函数:
renderItem和事件处理器使用useCallback缓存 - 避免内联样式:使用
StyleSheet.create或useMemo缓存样式对象 keyExtractor:提供稳定唯一的 key
使用 FlashList(@shopify/flash-list)替代 FlatList:
- 使用 RecyclerListView 内核,实现真正的视图回收
- 不再销毁/创建新组件,而是复用已有组件实例
- 估算列表项尺寸,减少布局抖动
- 性能比 FlatList 提升 5-10 倍
- 需要正确设置
estimatedItemSize
使用原生列表(高度定制场景):
- 直接使用 iOS UICollectionView / Android RecyclerView
- 通过 Fabric Component 暴露给 JS
3.2 键盘遮挡输入框
3.2.1 问题描述
- 键盘弹出时遮挡输入框
- 不同设备键盘高度不同
- Android 和 iOS 行为差异大
- ScrollView 内的输入框定位困难
3.2.2 解决方案
内置方案:
KeyboardAvoidingView:根据键盘高度调整内容位置behavior属性:iOS 通常用"padding",Android 用"height"或不设置keyboardVerticalOffset:用于补偿导航栏等固定元素的高度
ScrollView的keyboardShouldPersistTaps:控制点击行为TextInput的scrollEnabled/onFocus手动滚动
第三方方案:
react-native-keyboard-aware-scroll-view:自动滚动到聚焦的输入框react-native-avoid-softinput:原生实现的键盘避让- Android
windowSoftInputMode配置:adjustResize/adjustPan
3.2.3 深入细节
- iOS 可以监听
keyboardWillShow(动画开始前),Android 只有keyboardDidShow(已显示后) - 这导致 Android 上键盘避让动画不够流畅
- 解决方案:Android 使用
WindowInsetsAnimation(API 30+)或react-native-keyboard-controller
3.3 图片相关问题
3.3.1 大图片内存溢出
- 高分辨率图片加载到内存中占用巨大空间(4000x3000 的图片 ≈ 48MB 原始内存)
- 多张大图同时显示导致 OOM crash
解决方案:
- 使用
react-native-fast-image配合合理的缓存策略 - 服务端提供多分辨率图片(CDN 图片处理)
- 使用
resizeMode和resizeMethod(Android)控制图片解码大小 - iOS: 使用
Image.resolveAssetSource()获取实际尺寸后按比例缩放 - 列表中图片使用懒加载(FlatList 的 viewport 机制)
3.3.2 图片缓存策略
- RN 内置 Image 组件的缓存行为在 iOS 和 Android 上不一致
- iOS 使用 NSURLCache(HTTP 缓存),Android 使用 OkHttp 缓存
- 推荐使用 react-native-fast-image 统一缓存策略
3.4 启动白屏 / 启动性能
3.4.1 问题描述
- App 启动后出现短暂白屏
- JS Bundle 加载和执行时间长
- 首屏渲染慢
3.4.2 原因分析
启动流程:
- 原生应用初始化
- JS 引擎初始化
- 加载 JS Bundle
- 执行 JS Bundle(所有全局代码)
- React 初始化,首次渲染
- 布局计算
- 原生视图创建和渲染
每一步都可能成为瓶颈。
3.4.3 解决方案
- 使用 Hermes:预编译字节码,跳过 JS 解析和编译步骤
- 启动屏 (Splash Screen):使用
react-native-splash-screen/expo-splash-screen,在原生层显示启动图,JS 加载完成后隐藏 - 减少 Bundle 体积:Tree Shaking、代码分割、移除无用依赖
- 延迟加载:非首屏需要的模块使用
require()延迟加载(而非顶层import) - Inline Requires:Metro 配置
inlineRequires: true,将模块加载延迟到首次使用 - RAM Bundle:将模块按需加载
- 减少全局初始化代码:避免在模块顶层执行耗时操作
- 原生模块懒加载(TurboModules):只在需要时初始化原生模块
3.5 平台差异适配
3.5.1 常见差异
| 差异项 | iOS | Android |
|---|---|---|
| 阴影 | shadowColor/Offset/Opacity/Radius | elevation(不支持精细控制) |
| 字体 | SF Pro (系统) | Roboto (系统) |
| StatusBar | 默认不透明 | 默认透明/半透明 |
| 返回按钮 | 无物理返回键 | 有物理/虚拟返回键 |
| 触摸反馈 | 无 ripple | ripple 效果 |
| 输入法 | 可控 | 行为多样(厂商定制) |
| 权限 | Info.plist 声明 | AndroidManifest + 运行时申请 |
| 通知 | APNs | FCM |
| WebView | WKWebView | Chromium |
3.5.2 适配方案
Platform.OS:运行时判断平台Platform.select():根据平台返回不同值- 文件后缀:
Component.ios.tsx/Component.android.tsx Platform.Version:获取系统版本号Dimensions/useWindowDimensions:获取屏幕尺寸- 安全区域:
react-native-safe-area-context
3.6 复杂动画实现
3.6.1 问题
- 复杂的手势驱动动画在 JS 线程执行会卡顿
- 多个动画组合和串联的控制复杂
- 布局动画(LayoutAnimation)在 Android 上的兼容性问题
3.6.2 解决方案
- react-native-reanimated + gesture-handler:将动画和手势全部移到 UI 线程
- LayoutAnimation(简单场景):一行代码实现布局变化动画
- iOS 支持良好
- Android 需要在 MainApplication 中开启:
UIManager.setLayoutAnimationEnabledExperimental(true)
- Lottie (react-native-lottie):播放 After Effects 导出的动画文件
- 原生渲染,性能好
- 适合复杂的设计动画
3.7 内存泄漏
3.7.1 常见内存泄漏场景
- 未清理的定时器:
setInterval/setTimeout没有在组件卸载时清除 - 未取消的订阅:事件监听、数据订阅没有取消
- 未取消的网络请求:组件卸载后请求回调中仍更新 state
- 闭包引用:useEffect/useCallback 中的闭包持有大对象引用
- 全局变量累积:模块级变量持续增长
- 图片缓存过大:大量图片缓存未清理
- 导航栈过深:Stack Navigator 中页面不断 push 但不 pop
3.7.2 排查方法
- Xcode Instruments → Allocations/Leaks:查看内存分配和泄漏
- Android Studio Profiler → Memory:查看堆内存、分配追踪
- Hermes 采样 profiler:分析 JS 内存使用
- Flipper + LeakCanary (Android):自动检测内存泄漏
- Chrome DevTools → Memory 快照:对比不同时间点的内存快照
__DEV__模式下的 Warning:React 会警告卸载组件上的 state 更新
3.7.3 解决方案
- useEffect 中必须返回清理函数
- 使用
AbortController取消网络请求 - 使用
useRef标记组件挂载状态(isMounted.current) - 合理使用导航:replace 代替 push,定期 reset 导航栈
- 大对象使用后主动置 null
- 图片缓存设置最大容量
3.8 多语言 / 国际化
3.8.1 技术方案
react-native-localize:获取设备语言设置react-i18next/i18next:国际化框架- 支持命名空间
- 支持变量插值
- 支持复数规则
- 支持语言回退
3.8.2 RTL(从右到左)布局
- 阿拉伯语、希伯来语等需要 RTL 布局
I18nManager.forceRTL(true)强制 RTL- Flexbox 的
flexDirection: 'row'在 RTL 下自动反转 - 需要注意图标、动画方向的适配
3.9 Debugging 痛点
3.9.1 常见问题
- Red Screen of Death (RSOD):JS 运行时错误
- Yellow Box:警告信息
- 调试时性能与生产环境差异大(DEV 模式下有大量检查)
- Remote Debugger(Chrome)下行为与真机不一致(使用 Chrome V8 而非 JSC/Hermes)
- Source Map 不准确导致断点位置偏移
3.9.2 调试工具
-
Flipper:Meta 官方调试工具
- 网络请求查看
- Layout Inspector
- React DevTools 集成
- 数据库查看
- 原生日志查看
- 自定义插件
-
React DevTools:查看组件树、props、state
-
Hermes Debugger:直接在 Hermes 引擎上调试(通过 Chrome DevTools Protocol)
- 比 Remote Debugger 更准确(不切换 JS 引擎)
- 支持断点、变量查看、性能分析
-
console.log + Metro 日志:最基础的调试方式
3.10 安装与构建问题
3.10.1 常见构建问题
- CocoaPods 版本冲突:多个库依赖不同版本的同一原生库
- 解决:
pod install --repo-update,或在 Podfile 中手动指定版本
- 解决:
- Android Gradle 构建失败:
- 依赖版本冲突:
resolutionStrategy - NDK 版本不匹配:统一 NDK 版本
- 内存不足:增加 Gradle JVM 内存
-Xmx4g
- 依赖版本冲突:
- Xcode 签名问题:证书、Provisioning Profile 配置
- 新架构迁移:旧的 NativeModules 不兼容 TurboModules
- 使用 Interop Layer 过渡
- 逐步迁移到 Codegen 接口
3.10.2 依赖管理
react-native-config:管理不同环境(dev/staging/production)的配置patch-package:修补第三方库的 bug(不等上游发版)auto-linking:RN 0.60+ 自动链接原生依赖pod-install:自动化 CocoaPods 安装
3.11 OTA 热更新的坑
3.11.1 常见问题
- 更新包过大导致下载失败
- 更新后崩溃且无法回滚
- 多版本共存的兼容性
- 审核合规问题(Apple 对热更新的限制)
3.11.2 解决方案
- 差量更新减少包大小
- 实现回滚机制(CodePush 内置)
- 灰度发布(按百分比/用户群推送)
- 版本号管理:
targetBinaryVersion匹配原生版本 - Apple 要求:热更新不能改变 App 的主要用途和功能
3.12 大型项目的代码组织
3.12.1 Monorepo 方案
- 使用 Yarn Workspaces / Turborepo / Nx
- 共享代码(UI 组件、工具函数、类型定义)
- 独立的 App 包和库包
- 统一的依赖版本管理
3.12.2 模块化架构
- 按功能域划分模块(Feature-based)
- 每个模块独立的路由、状态、API
- 模块间通过定义好的接口通信
- 支持模块的独立开发和测试
第四部分:性能优化八股文与细节
4.1 渲染性能优化
4.1.1 避免不必要的重渲染
React.memo:
- 对函数组件的 props 进行浅比较(shallow compare)
- 如果 props 未变化,跳过重渲染
- 浅比较规则:基本类型用
===,引用类型比较引用地址 - 可以传入自定义比较函数
arePropsEqual - 注意:如果组件本身很简单,
React.memo的比较开销可能超过重渲染开销
useMemo:
- 缓存计算结果
- 依赖项不变时返回缓存值
- 适用于:昂贵的计算、创建新对象/数组(避免引用变化触发子组件重渲染)
- 注意:useMemo 本身有开销(存储缓存值 + 比较依赖项),简单计算不需要 useMemo
useCallback:
- 缓存函数引用
- 本质是
useMemo(() => fn, deps)的语法糖 - 主要用于:传递给
React.memo包裹的子组件的回调函数 - 如果不配合
React.memo使用,useCallback几乎没有优化效果
重渲染的触发条件:
- 自身 state 变化
- 父组件重渲染(即使 props 未变化,除非使用
React.memo) - Context value 变化(消费了该 Context 的组件)
- 强制更新(
forceUpdate)
常见的性能陷阱:
// 陷阱 1:内联对象/数组 → 每次渲染创建新引用
<View style={{ flex: 1 }} /> // 每次渲染都是新对象
// 解决:使用 StyleSheet.create 或 useMemo
// 陷阱 2:内联函数 → 每次渲染创建新函数
<Button onPress={() => doSomething()} />
// 解决:使用 useCallback(配合 React.memo 的子组件才有意义)
// 陷阱 3:Context 传递新对象
<Context.Provider value={{ theme, setTheme }}>
// 每次渲染 value 都是新对象 → 所有消费者重渲染
// 解决:使用 useMemo 缓存 value
4.1.2 列表渲染优化
FlatList 性能参数详解:
| 参数 | 默认值 | 作用 | 调优建议 |
|---|---|---|---|
windowSize | 21 | 渲染窗口(屏幕数) | 减小可降低内存,但增加白屏风险 |
maxToRenderPerBatch | 10 | 每批渲染的最大数量 | 减小可降低单帧渲染压力 |
updateCellsBatchingPeriod | 50ms | 批量更新间隔 | 增大可减少渲染频率 |
initialNumToRender | 10 | 首屏渲染数量 | 设置为首屏可见的数量 |
removeClippedSubviews | false | 移除不可见视图 | iOS 上启用,Android 慎用 |
FlashList 优化原理:
- 真正的视图回收:组件实例被复用,只更新 props
- Cell 类型系统:相同类型的 Cell 才会互相回收复用
overrideItemLayout:精确控制每个 item 的尺寸estimatedItemSize:估算平均尺寸,减少布局跳动prepareForRecycling:组件被回收时清理状态
4.1.3 图片渲染优化
- 使用正确的图片尺寸(匹配显示尺寸 × 像素密度)
- 使用 WebP 格式(体积更小)
- 实现渐进式加载(模糊预览 → 清晰图片)
- 使用 react-native-fast-image 的缓存策略
- 列表中图片使用固定宽高(避免布局跳动)
- 考虑使用
react-native-blurhash做占位图
4.1.4 组件拆分策略
- 大组件拆分:将大组件拆分为小组件,state 变化只影响小组件重渲染
- 状态下沉:将 state 放到最需要它的组件中(不要提升到不必要的高层)
- 内容提升(Lifting Content Up):将不变的 JSX 作为 children 传入,避免重渲染
- 渲染分离:将频繁更新的部分(如动画值显示)与静态部分分离
4.2 JS 线程性能优化
4.2.1 减少 JS 线程工作量
- 复杂计算使用
InteractionManager.runAfterInteractions()延迟执行 - 使用
requestAnimationFrame分批处理 - 动画使用
useNativeDriver: true(Animated API)或使用 Reanimated - 大数据处理考虑使用 Web Worker(通过
react-native-worker-threads)
4.2.2 避免 JS 线程阻塞
- 避免在渲染中进行同步的大计算
- JSON 解析大数据时使用流式解析或分批解析
- 正则表达式避免回溯灾难(Catastrophic Backtracking)
- 避免深层对象的深拷贝
4.2.3 InteractionManager 详解
runAfterInteractions:在所有动画和交互完成后执行回调- 原理:维护一个交互任务队列,当没有正在进行的动画/触摸时,执行排队的任务
- 适用场景:页面跳转后加载数据、动画完成后执行计算
- 可以返回
{ cancel }用于取消排队的任务
4.3 内存优化
4.3.1 内存使用模式
- JS Heap:JS 对象占用的内存
- Native Heap:原生视图、图片等占用的内存
- Graphics Memory:GPU 纹理占用的内存
4.3.2 具体优化策略
JS 层面:
- 避免创建不必要的对象(对象池模式)
- 大数组使用分页加载
- 及时解除大对象的引用
- 使用 WeakRef / WeakMap 管理缓存
- 避免闭包持有大对象
图片层面:
- 设置图片缓存上限
- 及时清理不需要的图片缓存(
Image.queryCache()+clearCache()) - 使用合适分辨率的图片
- 列表中的图片在滑出视口后释放
原生视图层面:
removeClippedSubviews:移除不可见视图的原生实例- react-native-screens:被遮挡的页面可以被系统回收
- 避免深层嵌套的视图层级
4.3.3 内存监控
- iOS:Xcode Memory Graph Debugger
- Android:Android Studio Memory Profiler
- 自定义内存监控:通过原生模块定期报告内存使用
- Hermes:使用
HermesInternal.getInstrumentedStats()获取内存统计
4.4 启动性能优化(深入)
4.4.1 启动时间分解
冷启动流程(毫秒级分解):
- 进程创建(~50-200ms):操作系统创建进程
- 原生初始化(~100-500ms):
- Application 创建
- 原生模块注册和初始化(旧架构:全量初始化)
- JS 引擎初始化
- JS Bundle 加载(~50-500ms):
- 文件读取
- Hermes 字节码加载 / JSC 源码解析
- JS 执行(~100-1000ms):
- 全局代码执行
- 模块初始化
- React 组件树构建
- 首次渲染(~50-200ms):
- Yoga 布局计算
- 原生视图创建
- 可交互(TTI):用户可以操作的时间点
4.4.2 优化策略详解
Bundle 体积优化:
- 使用
metro.config.js的transformer.minifierConfig优化压缩 console.log在生产环境移除:使用babel-plugin-transform-remove-console- 分析 Bundle 组成:使用
react-native-bundle-visualizer - 移除未使用的导出:Tree Shaking(Metro 对此支持有限,需关注)
- 图片资源优化:使用合适的格式和压缩
Inline Requires 详解:
// 关闭 inline requires 时
import { HeavyModule } from './HeavyModule'; // 应用启动时立即执行
// 开启 inline requires 后(Metro 自动转换)
// import 被转换为 lazy require
let _HeavyModule;
function getHeavyModule() {
if (!_HeavyModule) {
_HeavyModule = require('./HeavyModule');
}
return _HeavyModule;
}
- Metro 配置:
transformer: { inlineRequires: true } - 效果:模块在首次使用时才加载,减少启动时的初始化工作
- 注意:有些模块有副作用(side effects),不适合延迟加载
并行初始化:
- 原生初始化和 JS 加载并行执行
- 显示 Splash Screen 时在后台预加载数据
- 使用
InteractionManager延迟非关键初始化
4.4.3 启动性能测量
- iOS:Xcode Instruments → Time Profiler
- Android:
adb shell am start -W测量启动时间 - React Native:
performance.now()或Date.now()打点 - Hermes:内置的采样 profiler(
HermesInternal.enableSamplingProfiler()) - Flipper:查看启动阶段的各种指标
4.5 网络性能优化
4.5.1 请求优化
- 请求合并:使用 GraphQL 减少请求次数
- 请求缓存:React Query / SWR 的缓存策略
- 请求优先级:关键数据先加载,非关键数据延迟加载
- 取消无效请求:页面离开时取消进行中的请求
- 压缩:Gzip/Brotli 压缩请求和响应
- 连接复用:HTTP/2 多路复用
4.5.2 离线支持
- 本地数据缓存(MMKV / SQLite / WatermelonDB)
- 乐观更新:先更新 UI,后发送请求
- 离线队列:记录离线操作,恢复网络后批量发送
- NetInfo 监听网络状态变化
4.6 动画性能优化
4.6.1 60fps 的基本要求
- 每帧预算:1000ms / 60 = 16.67ms
- JS 线程和 UI 线程都需要在 16.67ms 内完成工作
- 任何线程超过预算都会导致掉帧
4.6.2 Animated API 性能技巧
- useNativeDriver: true:
- 将动画序列序列化后发送到原生端
- 动画在 UI 线程执行,不经过 JS 线程
- 限制:只支持非布局属性(transform, opacity),不支持 width/height/margin 等
- 原理:将动画描述(起始值、目标值、缓动函数、持续时间)一次性发送给原生端
- 避免在动画过程中更新 state
- 使用
Animated.event的useNativeDriver处理滑动联动
4.6.3 Reanimated 性能最佳实践
- 避免在 worklet 中使用
runOnJS(会产生跨线程通信) - Shared Values 使用基本类型(number, string, boolean),避免复杂对象
useAnimatedStyle中只依赖 Shared Values,不依赖 React state- 合理使用
cancelAnimation避免动画堆积 - 利用
withRepeat、withSequence等高级 API 减少 worklet 复杂度
4.7 包体积优化
4.7.1 分析工具
react-native-bundle-visualizer:可视化 Bundle 组成source-map-explorer:分析 Source Mapnpx react-native-asset-bundle-size:分析资源文件大小
4.7.2 优化策略
- 移除未使用的依赖(
depcheck) - 使用更小的替代库(moment.js → dayjs, lodash → lodash-es 或单独导入)
- 图片资源压缩(TinyPNG、WebP)
- 字体文件精简(只包含使用到的字符子集)
- ProGuard (Android) 代码混淆和压缩
- Bitcode (iOS) 优化
- Hermes 字节码通常比 JS 源码更小
4.7.3 代码分割
- React.lazy + Suspense(Web 上有效,RN 中有限支持)
- 手动代码分割:使用
require()动态加载 - Metro 的
serializer.customSerializer自定义分割逻辑
4.8 持续性能监控
4.8.1 开发时监控
-
Performance Monitor(RN 内置):
- JS Thread FPS
- UI Thread FPS
- RAM 使用
- 通过 Dev Menu 开启
-
Systrace (Android):
- 系统级的性能追踪
- 可以看到每个方法的耗时
react-native-systrace集成
-
Xcode Instruments:
- Time Profiler:CPU 使用分析
- Core Animation:FPS 和 GPU 分析
- Allocations:内存分配分析
- Leaks:内存泄漏检测
4.8.2 生产环境监控
- 自定义性能打点上报
- 关键指标:启动时间、页面加载时间、帧率、崩溃率
- 使用 Sentry / Bugsnag / Firebase Performance 等 APM 工具
- 自定义原生模块采集 FPS 和内存数据
4.9 React Compiler(React Forget)
- React 19 引入的自动编译器
- 自动为组件添加 memoization(自动 memo, useMemo, useCallback)
- 分析组件代码,识别可以安全缓存的值
- 减少手动优化的需要
- 在 RN 新架构中逐步支持
第五部分:八股文中的对比
5.1 React Native vs Flutter
| 维度 | React Native | Flutter |
|---|---|---|
| 语言 | JavaScript/TypeScript | Dart |
| 渲染方式 | 原生组件(通过 Bridge/JSI) | 自绘引擎(Skia/Impeller) |
| UI 一致性 | 使用原生组件,平台差异大 | 自绘,跨平台完全一致 |
| 性能 | 接近原生(新架构提升大) | 接近原生(自绘有 GPU 开销) |
| 热重载 | 支持(Fast Refresh) | 支持(Hot Reload) |
| OTA 热更新 | 支持(CodePush 等) | 不支持(AOT 编译) |
| 生态 | npm 生态,库丰富 | pub.dev,快速增长 |
| 学习曲线 | Web 开发者友好 | 需学习 Dart |
| 包体积 | 较小 | 较大(包含 Skia 引擎) |
| 动画 | Reanimated(UI线程) | 内置动画系统 |
| 原生集成 | 方便(大量原生模块) | Platform Channel(相对复杂) |
| 社区 | 更成熟、更大 | 快速增长 |
| 使用大厂 | Meta, Microsoft, Shopify | Google, BMW, Alibaba |
| 架构 | 基于 React,JSI/Fabric | Widget 树,Element 树,RenderObject 树 |
核心区别:
RN 使用原生组件渲染(一个 <Text> 最终是 UILabel 或 TextView),Flutter 使用自绘引擎从像素级别绘制所有UI。这意味着:
- RN 天然适配平台风格(平台外观变化自动跟随)
- Flutter UI 完全可控(但需手动适配平台风格)
- RN 的性能瓶颈在 JS ↔ Native 通信
- Flutter 的性能瓶颈在 GPU 渲染管线
5.2 React Native vs 原生开发
| 维度 | React Native | 原生(iOS/Android) |
|---|---|---|
| 开发效率 | 一套代码双端 | 需要分别开发 |
| 性能 | 接近原生(90-95%) | 100% |
| 更新方式 | 热更新 + 应用商店 | 仅应用商店 |
| 团队要求 | 全栈/前端开发者 | 需要 iOS/Android 专家 |
| 复杂 UI | 受限(需原生辅助) | 完全灵活 |
| 系统 API | 通过 Bridge/JSI | 直接访问 |
| 调试 | 相对复杂(多层调试) | 成熟的调试工具 |
| 第三方SDK | 需要封装 | 直接使用 |
| 维护成本 | 较低(一套代码) | 较高(两套代码) |
| 适用场景 | 业务类 App、快速迭代 | 高性能、系统级应用 |
5.3 React Native vs Weex vs Uni-app
| 维度 | React Native | Weex | Uni-app |
|---|---|---|---|
| 基础框架 | React | Vue 2 | Vue 2/3 |
| 渲染 | 原生组件 | 原生组件 | 原生 + WebView |
| 维护方 | Meta | Apache (原阿里) | DCloud |
| 社区活跃度 | 非常高 | 低 (几乎停滞) | 国内高 |
| 跨端范围 | iOS + Android | iOS + Android + Web | iOS + Android + Web + 小程序 |
| 生态 | 极丰富 | 较少 | 丰富(国内) |
| 稳定性 | 高 | 中 | 中高 |
5.4 旧架构 vs 新架构对比
| 维度 | 旧架构(Bridge) | 新架构(Fabric + JSI) |
|---|---|---|
| 通信方式 | JSON 序列化 + 异步 Bridge | JSI 直接调用(同步/异步) |
| 序列化开销 | 有(JSON 序列化/反序列化) | 无(直接内存访问) |
| 渲染器 | 旧 UI Manager | Fabric |
| 模块系统 | NativeModules(全量初始化) | TurboModules(懒加载) |
| 类型安全 | 无保证 | Codegen 生成类型安全代码 |
| 同步调用 | 不支持 | 支持 |
| 并发渲染 | 不支持 | 支持 |
| Shadow Tree | 可变 | 不可变(Immutable) |
| 线程模型 | 固定三线程 | 灵活,任意线程可渲染 |
| 布局信息读取 | 异步回调 | 同步读取 |
5.5 Hermes vs JavaScriptCore vs V8
| 维度 | Hermes | JavaScriptCore | V8 |
|---|---|---|---|
| 编译方式 | AOT(预编译字节码) | JIT(运行时编译) | JIT(分层编译) |
| 启动速度 | 最快 | 中等 | 最慢 |
| 运行时性能 | 中等 | 中等 | 最快 |
| 内存占用 | 最低 | 中等 | 最高 |
| 包体积影响 | 字节码更小 | 需包含 JS 源码 | 引擎体积大 |
| GC 策略 | 分代 GC + 增量标记 | 分代 GC | 分代 GC + 增量标记 + 并发 GC |
| 调试支持 | Chrome DevTools Protocol | Safari Inspector / Chrome | Chrome DevTools |
| RN 默认 | 是(0.70+) | 否(旧版默认) | 否(第三方) |
| Proxy 支持 | 有限 | 完整 | 完整 |
| eval 支持 | 不支持 | 支持 | 支持 |
5.6 FlatList vs SectionList vs FlashList vs RecyclerListView
| 维度 | FlatList | SectionList | FlashList | RecyclerListView |
|---|---|---|---|---|
| 回收机制 | 卸载不可见组件 | 卸载不可见组件 | 真正的视图回收 | 真正的视图回收 |
| 性能 | 中等 | 中等 | 优秀 | 优秀 |
| 分组支持 | 不支持 | 支持 | 支持(通过类型) | 支持 |
| API 易用性 | 简单 | 简单 | 简单(兼容 FlatList) | 较复杂 |
| 内存效率 | 一般 | 一般 | 优秀 | 优秀 |
| 空白区域 | 常见 | 常见 | 极少 | 极少 |
| 维护方 | React Native | React Native | Shopify | Flipkart |
FlashList 为何性能更好:
- 真正的视图回收:FlatList 滑出屏幕的组件会被卸载(unmount),新的组件会被挂载(mount)。FlashList 滑出的组件保留实例,只更新 props(类似原生 RecyclerView)
- 减少内存分配:不频繁创建/销毁组件,减少 GC 压力
- 减少布局计算:复用已有的布局信息
- Cell 类型系统:只有相同类型的 Cell 才会互相回收,避免不必要的重渲染
5.7 React Navigation vs react-native-navigation (Wix)
| 维度 | React Navigation | react-native-navigation |
|---|---|---|
| 实现方式 | 纯 JS 实现 | 原生实现 |
| 导航栏 | JS 渲染 | 原生导航栏 |
| 页面容器 | React 组件 | 原生页面容器 |
| 性能 | 良好(配合 react-native-screens) | 更接近原生 |
| 定制化 | 高度可定制 | 受限于原生 API |
| 集成难度 | 简单 | 复杂(需改原生代码) |
| 社区活跃度 | 非常高 | 中等 |
| Deep Link | 内置支持 | 需手动处理 |
| 推荐程度 | 官方推荐 | 追求极致性能时使用 |
5.8 Animated vs LayoutAnimation vs Reanimated
| 维度 | Animated | LayoutAnimation | Reanimated 2/3 |
|---|---|---|---|
| 执行线程 | JS(可开启 native driver) | UI 线程 | UI 线程 |
| API 风格 | 声明式 | 声明式 | 声明式 + 命令式 |
| 支持属性 | transform, opacity(native driver)/ 全部 | 布局属性 | 全部 |
| 手势联动 | 有限 | 不支持 | 完美 |
| 复杂度 | 中等 | 简单 | 较高 |
| 可中断 | 有限 | 不可中断 | 可中断 |
| 条件逻辑 | 不支持 | 不支持 | 支持(worklet) |
| 适用场景 | 简单动画 | 布局变化 | 复杂交互动画 |
5.9 AsyncStorage vs MMKV vs SQLite vs Realm
| 维度 | AsyncStorage | MMKV | SQLite | Realm |
|---|---|---|---|---|
| API 类型 | 异步 | 同步(JSI) | 异步/同步 | 同步 |
| 数据模型 | Key-Value | Key-Value | 关系型 | 对象型 |
| 性能 | 慢 | 极快(30x) | 中等 | 快 |
| 数据量 | 少量 | 中等 | 大量 | 大量 |
| 查询能力 | 无 | 无 | SQL 查询 | 对象查询 |
| 加密 | 不支持 | 支持 | 需第三方 | 支持 |
| 适用场景 | 简单配置 | 频繁读写的配置 | 结构化数据 | 复杂对象数据 |
5.10 Redux vs MobX vs Zustand vs Jotai vs Recoil
| 维度 | Redux | MobX | Zustand | Jotai | Recoil |
|---|---|---|---|---|---|
| 范式 | Flux | 响应式 | Flux-lite | 原子化 | 原子化 |
| 代码量 | 多 | 少 | 极少 | 极少 | 少 |
| 学习曲线 | 高 | 中 | 低 | 低 | 中 |
| DevTools | 优秀 | 良好 | 良好 | 良好 | 良好 |
| TypeScript | 需配置 | 良好 | 优秀 | 优秀 | 良好 |
| Bundle 大小 | 较大 | 较大 | 极小(~1KB) | 极小 | 中等 |
| 精确更新 | 需 selector | 自动 | selector | 自动 | selector |
| 中间件 | 丰富 | 有限 | 支持 | 有限 | 有限 |
| 服务端状态 | RTK Query | mobx-query | 无内置 | 无内置 | 无内置 |
| 适用规模 | 大型 | 中大型 | 中小型 | 中小型 | 中型 |
5.11 CodePush vs Expo Updates vs 自建热更新
| 维度 | CodePush | Expo Updates | 自建方案 |
|---|---|---|---|
| 维护方 | Microsoft | Expo | 自己 |
| 差量更新 | 支持 | 支持 | 需自行实现 |
| 回滚 | 自动 | 手动 | 需自行实现 |
| 灰度发布 | 支持 | 支持(EAS Update) | 需自行实现 |
| 统计 | 内置 | EAS 仪表板 | 需自行实现 |
| 服务可控性 | 低(微软服务) | 中(Expo 服务) | 高(完全自控) |
| 成本 | 免费(有限制) | 免费+付费 | 服务器成本 |
| 集成难度 | 低 | 低(Expo 项目) | 高 |
5.12 Class Component vs Function Component
| 维度 | Class Component | Function Component |
|---|---|---|
| 语法 | ES6 Class | 函数 |
| 状态管理 | this.state + setState | useState |
| 生命周期 | 生命周期方法 | useEffect |
| 逻辑复用 | HOC / Render Props | 自定义 Hooks |
| this 绑定 | 需要处理 | 无 this |
| 性能 | 实例创建开销 | 闭包开销 |
| 代码量 | 多 | 少 |
| Error Boundary | 支持 | 不支持(仍需 Class) |
| Concurrent Mode | 部分支持 | 完全支持 |
| 未来方向 | 维护 | 推荐 |
5.13 ScrollView vs FlatList vs VirtualizedList
| 维度 | ScrollView | FlatList | VirtualizedList |
|---|---|---|---|
| 渲染方式 | 全量渲染 | 虚拟化(窗口渲染) | 虚拟化 |
| 内存占用 | 高(全部子组件) | 低(只渲染可见区域) | 低 |
| 适用数据量 | < 100 项 | 大量 | 大量 |
| 嵌套 ScrollView | 不建议 | 不建议 | 不建议 |
| 抽象级别 | 基础 | 高级 | 底层 |
| API 复杂度 | 简单 | 中等 | 较复杂 |
5.14 useEffect vs useLayoutEffect
| 维度 | useEffect | useLayoutEffect |
|---|---|---|
| 执行时机 | Commit 后异步执行 | Commit 的 Layout 阶段同步执行 |
| 阻塞渲染 | 不阻塞 | 阻塞(在绘制前执行) |
| 适用场景 | 数据获取、订阅、日志 | DOM 测量、同步 DOM 操作 |
| 性能影响 | 小 | 大(阻塞绘制) |
| RN 中 | 常用 | 需要在渲染前读取/修改布局时 |
5.15 受控组件 vs 非受控组件
| 维度 | 受控组件 | 非受控组件 |
|---|---|---|
| 数据来源 | React state | DOM/原生 |
| 更新方式 | onChange + setState | ref 获取值 |
| 实时验证 | 容易 | 困难 |
| 性能 | 每次输入触发重渲染 | 不触发重渲染 |
| RN TextInput | value + onChangeText | defaultValue + ref |
| 表单库 | react-hook-form, Formik | react-hook-form (默认非受控) |
5.16 Stack vs Tab vs Drawer Navigator
| 维度 | Stack | Tab | Drawer |
|---|---|---|---|
| UI 形式 | 堆叠式页面 | 底部/顶部标签 | 侧边抽屉 |
| 页面保持 | 全部保持在栈中 | 全部保持 | 全部保持 |
| 内存 | 栈越深越多 | 固定 | 固定 |
| 适用场景 | 层级导航 | 主导航 | 辅助导航 |
| 手势 | 右滑返回(iOS) | 无 | 左滑打开 |
5.17 Expo vs Bare React Native
| 维度 | Expo (Managed) | Bare React Native |
|---|---|---|
| 上手难度 | 极低 | 中等 |
| 原生代码 | 不可直接修改(需 prebuild) | 完全控制 |
| 构建 | EAS Build(云端) | 本地 Xcode/AS |
| 原生库 | 受限(需 expo 插件) | 任意 |
| OTA 更新 | EAS Update | CodePush 等 |
| 文件系统 | 受限 | 完全访问 |
| 推送通知 | expo-notifications | 自行集成 |
| 适用阶段 | 原型/中小项目 | 大型/复杂项目 |
第六部分:高难度深底层原理问题
6.1 JSI 的深层工作机制
6.1.1 Host Object 原理
JSI 的核心创新是 Host Object(宿主对象)。在传统 Bridge 模式下,JS 和 Native 通过 JSON 字符串通信。JSI 引入了 Host Object 的概念:
- C++ 对象暴露给 JS:C++ 创建一个继承
jsi::HostObject的对象 - 属性拦截:JS 访问该对象的属性时,调用 C++ 的
get()方法 - 方法调用:JS 调用该对象的方法时,直接执行 C++ 代码
- 零拷贝:JS 持有的是 C++ 对象的引用,不需要序列化
本质上:JSI 让 JS 直接操作 C++ 对象的指针(通过引用包装),消除了序列化的开销。
6.1.2 JSI 与引擎的解耦
JSI 定义了一组抽象接口(jsi::Runtime),每个 JS 引擎需要实现这些接口:
evaluateJavaScript():执行 JS 代码createObject():创建 JS 对象createFunctionFromHostFunction():将 C++ 函数暴露给 JSglobal():获取全局对象
通过这层抽象,React Native 可以无缝切换 JS 引擎(JSC → Hermes → V8),上层代码无需修改。
6.1.3 JSI 的线程安全问题
- JSI 对象(
jsi::Value,jsi::Object等)只能在创建它们的jsi::Runtime所在的线程访问 - 跨线程共享需要特殊处理:
- Shared Values(Reanimated)使用 C++ 层的互斥锁保护
- TurboModules 使用
CallInvoker将回调调度到正确的线程
6.2 Fabric 渲染流水线深入
6.2.1 三阶段详解
Render Phase:
- React 在 JS 线程执行 render(),生成 React Element Tree
- React Element 通过 JSI 调用 C++ 的
ShadowNodeFactory - 创建不可变的 C++ ShadowNode 树(每个节点包含 props 和子节点引用)
- ShadowNode 是不可变的:修改意味着创建新节点(Copy-on-Write 语义)
Commit Phase:
- 新的 ShadowNode 树被提交(Commit)
- 在后台线程执行 Yoga 布局计算(Layout)
- 执行 Tree Diffing:对比新旧 ShadowNode 树
- 生成 Mutation 指令列表(Create, Delete, Insert, Remove, Update)
- 使用引用相等性(指针比较)快速跳过未变化的子树
Mount Phase:
- 在主线程执行 Mutation 指令
- 创建/更新/删除原生视图
- 设置原生视图的属性和布局
- 这一阶段的操作必须在主线程执行(iOS UIKit / Android View System 的要求)
6.2.2 Immutable Shadow Tree 的意义
- 线程安全:不可变对象可以安全地在多线程间共享
- 高效 Diffing:如果两个节点指针相同,整个子树一定相同,可以跳过
- 支持并发:多个版本的 Shadow Tree 可以同时存在(Concurrent Mode)
- 可追溯:保留旧版本用于回滚
6.2.3 C++ 层的统一抽象
Fabric 使用 C++ 实现核心逻辑,跨 iOS/Android 共享:
- ShadowNode 和 ShadowTree 是 C++ 对象
- Yoga 布局计算在 C++ 层执行
- Tree Diffing 和 Mutation 生成在 C++ 层
- 只有最终的 Mount 操作调用平台特定的 API
6.3 TurboModules 底层机制
6.3.1 模块绑定流程
-
Codegen 阶段(编译时):
- 解析 TypeScript/Flow 接口定义
- 生成 C++ 的
NativeXxxCxxSpec类(接口定义) - 生成 ObjC++ / Java 的胶水代码
-
注册阶段(运行时):
- 模块工厂注册到
TurboModuleManager - 但不立即创建模块实例
- 模块工厂注册到
-
访问阶段(首次调用时):
- JS 代码访问
global.__turboModuleProxy('ModuleName') TurboModuleManager通过工厂创建模块实例- 将实例包装为 JSI Host Object
- 返回给 JS,后续调用直接通过 JSI
- JS 代码访问
6.3.2 同步调用的实现
- TurboModules 支持同步方法:JS 调用 → JSI → C++ → Native,在同一个调用栈中完成
- 适用于需要立即获取结果的场景(如读取本地存储)
- 注意:同步调用会阻塞 JS 线程,应该只用于极快的操作
6.3.3 与旧 NativeModules 的互操作
- Bridgeless 模式下提供 Interop Layer
- 旧的 NativeModules 通过 Interop 被包装为 TurboModules 接口
- 性能不如原生 TurboModules,但保证兼容性
6.4 Hermes 引擎深入
6.4.1 编译流程
JS 源码 → Hermes 编译器 → Hermes 字节码 (.hbc)
↓
1. Lexer(词法分析)
2. Parser(语法分析 → AST)
3. Semantic Validation(语义检查)
4. IR Generation(中间表示生成)
5. Optimization Passes(优化)
6. Bytecode Generation(字节码生成)
6.4.2 字节码格式 (.hbc)
- 文件头:魔数、版本、哈希
- 函数表:每个函数的字节码偏移量、参数信息
- 字符串表:所有字符串的存储(去重)
- 字节码段:实际的指令序列
- 正则表达式表:预编译的正则表达式
6.4.3 Hermes GC 详解
GenGC(分代垃圾回收):
-
Young Generation(新生代):
- 使用 semi-space copying 算法
- 分为 From Space 和 To Space
- 新对象分配在 From Space
- GC 时将存活对象从 From Space 复制到 To Space
- 然后交换两个 space
- 存活多次的对象晋升到 Old Generation
-
Old Generation(老年代):
- 使用 mark-compact 算法
- Mark 阶段:标记所有可达对象
- Compact 阶段:移动对象消除碎片
- 支持增量标记:将标记工作分散到多个时间片,避免长暂停
GC 优化策略:
- Allocation 策略:对小对象使用 bump-pointer allocation(极快)
- Write Barrier:当老年代对象引用新生代对象时,记录在 remembered set 中,避免全堆扫描
- 惰性扫描:延迟扫描大对象
- GC 暂停控制:目标是将 GC 暂停控制在毫秒级
6.4.4 Hermes 为何不支持 JIT
- 安全限制:iOS 不允许第三方 App 执行动态生成的可执行代码(W^X)
- 权衡:AOT 编译 + 优化的解释器 在移动端场景下,综合性能(启动 + 运行 + 内存)优于 JIT
- 确定性:AOT 编译产生确定的性能特征,不像 JIT 有 "warm-up" 期
6.5 Bridge 通信的底层实现
6.5.1 消息格式
Bridge 传输的消息格式:
[
moduleID, // Native 模块 ID(数字)
methodID, // 方法 ID(数字)
args // 参数数组(JSON 序列化)
]
6.5.2 批量处理
- JS 端不会立即发送每条消息,而是收集在队列中
- 在以下时机批量刷新队列:
- 定时刷新(默认 5ms)
- Native 回调 JS 时捎带(piggyback)
- 队列满时
- 手动调用
flushQueue
6.5.3 Bridge 的瓶颈量化
- 单条消息的序列化/反序列化:~0.01-0.1ms
- 大型消息(如长列表数据):可能需要几十毫秒
- 高频消息(如动画每帧更新):累积延迟导致掉帧
- 在 16.67ms 的帧预算内,Bridge 通信可能占用 30-50%
6.6 React 的 Lane 优先级模型
6.6.1 Lane 系统
React 18 使用 Lane 位标记系统管理更新优先级:
- 每个 Lane 是一个 32 位整数的一个 bit
- 多个 Lane 可以通过位运算组合
- 优先级从高到低:
SyncLane:同步更新(最高优先级)InputContinuousLane:连续输入(如拖动)DefaultLane:默认更新(如 setState)TransitionLane:过渡更新(startTransition)IdleLane:空闲更新
6.6.2 在 RN 中的应用
- 触摸事件处理:
SyncLane(立即响应) - 数据更新:
DefaultLane(正常优先级) - 页面切换动画:
TransitionLane(可中断) - 预加载数据:
IdleLane(空闲时执行)
6.6.3 优先级饥饿问题
- 高优先级更新持续到来时,低优先级更新可能一直得不到执行
- React 的解决方案:给每个更新设置过期时间(expiration time)
- 过期的更新会被提升为同步更新,强制执行
6.7 React Native 的事件循环与调度
6.7.1 JS 线程的事件循环
RN 的 JS 线程运行一个类似浏览器的事件循环:
- 执行同步代码
- 处理微任务(Microtask):Promise.then, queueMicrotask
- 处理宏任务(Macrotask):setTimeout, setInterval, I/O 回调
- 处理 Bridge/JSI 消息:来自 Native 的调用
- 执行 requestAnimationFrame 回调
6.7.2 与浏览器事件循环的区别
| 维度 | 浏览器 | React Native |
|---|---|---|
| 渲染时机 | RAF → Style → Layout → Paint → Composite | JS 计算 → Bridge/JSI → Native 渲染 |
| requestAnimationFrame | 与屏幕刷新同步 | 不保证与屏幕刷新同步 |
| requestIdleCallback | 支持 | 不支持(可 polyfill) |
| Web Worker | 支持 | 不原生支持 |
| MessageChannel | 用于 React 调度器 | 同样用于 React 调度器 |
6.7.3 React Scheduler
- React 使用自己的调度器(Scheduler)管理更新
- 在 RN 中通过
MessageChannel或setTimeout(fn, 0)实现调度 - 调度器维护一个优先级任务队列(小顶堆)
- 每次事件循环取出最高优先级的任务执行
- 如果任务执行时间超过时间片(默认 5ms),让出控制权
6.8 原生视图的生命周期管理
6.8.1 iOS UIView 在 RN 中的管理
- 创建:Fabric 的 Mount 阶段调用
ComponentViewFactory创建 UIView - 更新 Props:通过
updateProps:方法更新属性(diff 后只更新变化的属性) - 布局:设置
frame属性(由 Yoga 计算的坐标和尺寸) - 挂载:
addSubview:添加到父视图 - 卸载:
removeFromSuperview从父视图移除 - 销毁:当没有任何引用时由 ARC 自动回收
6.8.2 Android View 在 RN 中的管理
- 创建:
ViewManager.createViewInstance()创建 View - 更新 Props:
@ReactProp注解的方法被调用 - 布局:调用
measure()+layout()设置位置和大小 - 挂载:
addView()添加到 ViewGroup - 卸载:
removeView()从 ViewGroup 移除 - 销毁:
onDropViewInstance()清理资源
6.8.3 View Flattening
- React 中大量使用嵌套
<View>组件,但很多只用于布局,没有视觉效果 - View Flattening 优化:将这些 "纯布局" View 从原生视图层级中移除
- 只保留有视觉属性(背景色、边框、阴影等)的 View
- 减少原生视图数量 → 减少内存 → 加快渲染
- Fabric 自动执行 View Flattening
6.9 跨线程数据共享的挑战
6.9.1 问题本质
RN 中有多个线程(JS, UI, Shadow, Native Module),跨线程数据共享面临:
- 数据竞争:多线程同时读写同一数据
- 一致性:确保各线程看到的数据状态一致
- 性能:锁竞争会降低性能
6.9.2 RN 中的解决方案
旧架构:
- 通过 Bridge 消息传递,本质上是 "消息传递" 模式
- 各线程维护自己的数据副本
- 不存在共享可变状态
新架构:
- JSI 引入了共享引用的概念
- 使用 C++ 的
std::mutex和std::shared_mutex保护共享数据 - Fabric 的 Shadow Tree 使用不可变数据结构,避免并发修改
CallInvoker确保回调在正确的线程执行
Reanimated 的方案:
- Shared Values 使用 C++ 层的原子操作或锁保护
- worklet 运行在独立的 JS 运行时,与主 JS 运行时隔离
- 通过共享内存(而非消息传递)交换数据
6.10 Metro Bundler 的模块解析算法
6.10.1 解析流程
- 从入口文件(
index.js)开始 - 遇到
import/require时:- 相对路径(
./module):按路径查找 - 包名(
react-native):依次查找node_modules
- 相对路径(
- 文件扩展名解析顺序:
.ios.js→.native.js→.js(iOS 平台) - 目录解析:查找
index.js或package.json的main字段
6.10.2 平台特定模块
Metro 支持平台特定的模块解析:
Component.js // 通用
Component.ios.js // iOS 专用
Component.android.js // Android 专用
Component.native.js // iOS + Android 共用
Component.web.js // Web 专用
解析优先级:平台后缀 > .native > 通用
6.10.3 Metro 与 Webpack 的区别
| 维度 | Metro | Webpack |
|---|---|---|
| 目标平台 | React Native | Web(也可其他) |
| 代码分割 | 有限支持 | 完善支持 |
| Tree Shaking | 有限 | 完善 |
| HMR | Fast Refresh | Hot Module Replacement |
| 增量构建 | 内存缓存(极快) | 文件缓存 |
| 配置 | 简单 | 复杂但灵活 |
| 插件系统 | 简单 | 强大 |
6.11 React Native 的触摸事件处理底层
6.11.1 iOS 触摸事件链
- 系统通过
UIResponder链传递UITouch事件 - RN 的根视图
RCTRootContentView接收触摸事件 - 通过 hit-testing 找到目标视图
- 事件被包装为
RCTTouchEvent - 旧架构:序列化后通过 Bridge 发送到 JS
- 新架构:通过 JSI 直接传递到 JS
6.11.2 Android 触摸事件链
- Activity 通过
dispatchTouchEvent传递MotionEvent - RN 的
ReactRootView接收事件 - 通过
JSTouchDispatcher收集触摸点信息 - 事件被批量发送到 JS
6.11.3 手势冲突处理
- 原生手势(ScrollView 滑动)和 JS 手势可能冲突
disallowInterceptTouchEvent(Android):阻止父视图拦截触摸事件- Gesture Handler 使用独立的手势识别系统,与原生手势并行
- 通过手势竞争机制(simultaneousHandlers, waitFor)解决冲突
6.12 React Native 的 Accessibility 实现
6.12.1 iOS VoiceOver
accessible={true}:将组件标记为无障碍元素accessibilityLabel:映射到 UIView 的accessibilityLabelaccessibilityRole:映射到UIAccessibilityTraitsaccessibilityActions:自定义无障碍操作
6.12.2 Android TalkBack
accessible={true}:设置importantForAccessibilityaccessibilityLabel:映射到contentDescriptionaccessibilityRole:映射到className(TalkBack 根据 className 确定控件类型)
6.12.3 无障碍焦点管理
accessibilityElementsHidden(iOS)/importantForAccessibility(Android)accessibilityViewIsModal:将无障碍焦点限制在当前视图(模态窗口)- 自定义焦点顺序需要原生代码辅助
6.13 深入理解 Yoga 的 Flexbox 实现
6.13.1 Yoga 与 CSS Flexbox 的差异
| 特性 | CSS Flexbox | Yoga (RN) |
|---|---|---|
flexDirection 默认值 | row | column |
flex 简写 | flex-grow flex-shrink flex-basis | 等价于 flexGrow(简化) |
position: absolute | 相对于最近的 positioned 祖先 | 相对于父元素 |
overflow 默认值 | visible | hidden(Android)/ visible(iOS) |
| 百分比 padding | 参照父元素对应方向 | 参照父元素宽度 |
6.13.2 Yoga 的布局算法
- 确定主轴和交叉轴(根据 flexDirection)
- 收集弹性子项(flex > 0 的子项)
- 第一轮布局:
- 固定尺寸的子项直接确定大小
- 弹性子项根据 flexBasis 确定初始大小
- 分配剩余空间:
- 如果有剩余空间:按 flexGrow 比例分配
- 如果空间不足:按 flexShrink 比例收缩
- 确定交叉轴对齐(alignItems, alignSelf)
- 多行处理(flexWrap)
- 处理绝对定位子项
6.13.3 自定义测量函数
- 某些组件需要原生测量(如 Text 组件需要知道文本的渲染尺寸)
- Yoga 支持为节点设置自定义测量函数
- 测量函数在布局计算时被调用
- 返回节点的固有尺寸(intrinsic size)
- Text 组件的测量函数会调用原生文本排版引擎(iOS: TextKit, Android: StaticLayout)
6.14 跨平台字体渲染差异
6.14.1 文本渲染管线
iOS:
- TextKit 框架 → Core Text → Core Graphics → GPU
- 使用子像素抗锯齿(Subpixel Anti-aliasing)
- 支持动态字体大小(Dynamic Type)
Android:
- StaticLayout / DynamicLayout → Skia → OpenGL / Vulkan
- 使用灰度抗锯齿(Grayscale Anti-aliasing)
- 不同厂商可能定制字体渲染
6.14.2 常见问题
- 相同字体大小在不同平台显示不同
- 行高计算方式不同(iOS 和 Android 对
lineHeight的处理不同) - 字体加粗的 weight 映射不一致
- 自定义字体在 Android 上可能有 baseline 偏移
6.14.3 解决方案
includeFontPadding: false(Android):移除额外的字体 padding- 精确指定
lineHeight(避免使用默认值) - 统一使用自定义字体,减少平台差异
- 使用
textAlignVertical(Android)调整垂直对齐
6.15 新架构下的并发渲染
6.15.1 同步渲染 vs 并发渲染
同步渲染(传统):
- 一旦开始渲染,必须完成才能响应用户交互
- 大型组件树的渲染会阻塞用户交互
并发渲染(Concurrent):
- 渲染可以被中断和恢复
- 高优先级更新可以打断低优先级渲染
- 多个版本的 UI 可以同时 "准备"
6.15.2 Fabric 如何支持并发
- 不可变 Shadow Tree:每次渲染产生独立的 Shadow Tree 版本
- 多 Tree Revision:可以同时存在多个版本的 Shadow Tree
- 原子提交:Shadow Tree 的提交是原子操作(成功或失败,没有中间状态)
- 优先级中断:高优先级更新到来时,低优先级的渲染可以被丢弃
6.15.3 实际应用场景
- 搜索输入框 + 搜索结果列表:输入是高优先级,结果渲染是低优先级
- 页面切换:切换动画是高优先级,新页面内容渲染可以延迟
- 大列表滚动 + 数据更新:滚动是高优先级,数据更新可以在空闲时处理
6.16 React Native 的错误边界与崩溃恢复
6.16.1 JS 层面的错误处理
- Error Boundary:捕获子组件树中的 JS 错误,渲染 fallback UI
- global.ErrorUtils:RN 提供的全局错误处理(区别于 Web 的 window.onerror)
- Promise rejection:未捕获的 Promise rejection 可以通过
global.onunhandledrejection处理
6.16.2 原生层面的崩溃处理
- iOS:NSSetUncaughtExceptionHandler + signal handlers
- Android:Thread.setDefaultUncaughtExceptionHandler
- 崩溃报告工具:Sentry, Crashlytics, Bugsnag
6.16.3 JS 崩溃不会导致原生应用崩溃
- JS 错误会导致红屏(开发)或白屏(生产),但原生进程仍在运行
- 可以通过 Error Boundary 优雅降级
- 可以通过重新加载 JS Bundle 恢复(
DevSettings.reload()) - 但原生层面的 crash 会直接终止进程
6.17 内存映射(mmap)在 RN 中的应用
6.17.1 mmap 原理
- 将文件内容直接映射到进程的虚拟地址空间
- 不需要将整个文件读入内存(按需加载,Page Fault 时才从磁盘读取)
- 多进程可以共享同一映射(减少内存占用)
- 操作系统管理页面的换入/换出
6.17.2 Hermes 中的 mmap
- Hermes 字节码文件 (.hbc) 通过 mmap 加载
- 启动时不需要将整个字节码读入内存
- 只有执行到的代码页才会被加载
- 操作系统可以在内存压力时回收已加载但暂时不用的页面
6.17.3 MMKV 中的 mmap
- 数据文件通过 mmap 映射到内存
- 读写操作直接操作内存映射区域
- 由操作系统负责将脏页写回磁盘
- 进程崩溃时,操作系统仍会将脏页刷盘(大部分情况下数据不丢失)
- 比文件 I/O 快一个数量级
6.18 JavaScript → 原生渲染的完整链路
以一个简单的 <Text>Hello</Text> 为例,追踪其从 JS 到屏幕显示的完整过程:
新架构(Fabric + JSI):
-
JS 线程:
- React 执行 render(),创建
<Text>的 React Element - createElement 通过 JSI 调用 C++ 的
ShadowNodeFactory.createNode()
- React 执行 render(),创建
-
C++ 层:
- 创建
TextShadowNode(不可变的 C++ 对象) - 存储 props(text content, style 等)
TextShadowNode包含自定义测量函数
- 创建
-
Commit Phase(后台线程):
- 提交新的 Shadow Tree
- Yoga 执行布局计算
- 调用 Text 的自定义测量函数 → 调用原生文本排版引擎测量文本尺寸
- Tree Diffing 生成 Mutation 指令
-
Mount Phase(主线程):
- iOS:创建
RCTParagraphComponentView(UIView 子类),设置NSAttributedString,调用setNeedsDisplay - Android:创建
ReactTextView(TextView 子类),设置SpannableString,调用requestLayout
- iOS:创建
-
原生渲染管线:
- iOS:CoreAnimation → Metal/OpenGL → GPU → 屏幕
- Android:Canvas → Skia → OpenGL/Vulkan → GPU → 屏幕
整个过程从 JS 到屏幕,在新架构下可以在 1-2 帧(16-33ms)内完成。
6.19 React Native 的安全考量
6.19.1 JS Bundle 安全
- JS Bundle 是明文的(或 Hermes 字节码可被反编译)
- 敏感逻辑不应放在 JS 层
- 代码混淆:
metro.config.js配置混淆 - Hermes 字节码比 JS 源码更难逆向,但仍可反编译
6.19.2 网络通信安全
- SSL Pinning:验证服务器证书(react-native-ssl-pinning)
- 请求签名:防止篡改
- Token 安全存储:使用 Keychain (iOS) / Keystore (Android) 而非 AsyncStorage
6.19.3 数据存储安全
- Keychain (iOS) / Keystore (Android):加密存储敏感数据
- MMKV 加密模式:AES-128 加密
- SQLCipher:SQLite 加密扩展
- 避免在日志中输出敏感信息
6.20 Worklet 运行时的隔离与通信
6.20.1 双运行时架构(Reanimated)
Reanimated 在 UI 线程维护一个独立的 JS 运行时:
- 主 JS 运行时(JS 线程):运行 React 代码和业务逻辑
- Worklet 运行时(UI 线程):运行 worklet 函数
两个运行时通过 Shared Values(C++ 层)共享数据。
6.20.2 Worklet 的序列化与传输
- Babel 插件在编译时识别
'worklet'标记 - 提取函数体的 AST,分析闭包捕获的变量
- 将函数体序列化为字符串
- 将捕获的变量打包为初始化数据
- 运行时:在 UI 线程的 JS 运行时中 eval 函数字符串,绑定捕获变量
6.20.3 跨运行时调用的开销
runOnJS(fn)(args):从 UI 线程调度到 JS 线程- 参数需要序列化(简单类型直接传递,复杂类型 JSON 序列化)
- 有 1-2 帧的延迟
runOnUI(worklet)(args):从 JS 线程调度到 UI 线程- 类似的序列化和调度开销
- 应该尽量减少跨运行时调用
补充:常见高频面试题精选
Q1: React Native 是如何实现跨平台的?
React Native 通过抽象层实现跨平台。JS 层定义通用的组件和 API(如 <View>, <Text>),这些组件在不同平台上映射到不同的原生视图(iOS 的 UIView / Android 的 android.view.View)。通信层(Bridge/JSI)负责 JS 与 Native 的数据交换。布局引擎 Yoga(C++)跨平台共享布局计算逻辑。新架构中,Fabric 的 C++ 核心也是跨平台共享的。
Q2: 为什么 React Native 的性能比 WebView 好?
- WebView 的 DOM 操作需要经过浏览器渲染管线(Parse HTML → CSSOM → Layout → Paint → Composite),且在一个独立进程中
- RN 直接操作原生视图,不经过 DOM/CSSOM
- RN 的布局计算(Yoga)直接产出像素坐标,不需要 CSS 级联计算
- RN 可以利用原生组件的硬件加速
- 但 RN 的 JS ↔ Native 通信开销是其性能瓶颈(新架构大幅改善)
Q3: 什么情况下 React Native 不适用?
- 高度依赖 GPU 计算的应用(游戏、AR/VR)
- 需要大量底层系统 API 的应用(系统工具类)
- 对性能有极致要求的场景(如实时视频编辑)
- UI 高度依赖平台原生特性的应用
- 需要复杂自定义绘制的应用(如绘图工具)
Q4: React Native 新架构的核心价值是什么?
- 消除序列化开销:JSI 让 JS 直接操作 C++ 对象
- 支持同步操作:可以同步读取布局信息、同步调用 Native 方法
- 类型安全:Codegen 保证 JS 和 Native 接口一致
- 懒加载:TurboModules 按需初始化,加快启动
- 并发渲染:Fabric 支持 React 18 的 Concurrent Features
- 统一 C++ 核心:核心逻辑跨平台共享,减少平台差异
Q5: 如何监测和优化 React Native 应用的帧率?
-
监测:
- 开发时:RN Performance Monitor(Dev Menu 中开启)
- iOS:Instruments → Core Animation FPS
- Android:adb shell dumpsys gfxinfo
- Flipper 性能插件
-
常见掉帧原因及优化:
- JS 线程阻塞 → 将耗时操作移到原生或使用
InteractionManager - 过多重渲染 →
React.memo,useMemo,useCallback - 复杂列表 → 使用
FlashList, 优化FlatList参数 - 动画在 JS 线程 → 使用
Reanimated或useNativeDriver - 大量原生视图 → 减少视图嵌套层级(View Flattening)
- JS 线程阻塞 → 将耗时操作移到原生或使用
Q6: React Native 的 Fast Refresh 与 Hot Module Replacement 有什么区别?
-
Fast Refresh 是 React 专属的实现,知道 React 组件的边界
- 编辑函数组件时,只重新执行该组件,保留 state
- 编辑类组件时,重新挂载(不保留 state)
- 编辑非组件文件时,重新加载引用该文件的所有组件
- 语法错误不会导致全量刷新(修复后自动恢复)
-
HMR 是通用的模块热替换机制
- 不理解 React 组件的边界
- 总是需要手动定义
module.hot.accept - Fast Refresh 基于 HMR 实现,但添加了 React 特有的逻辑
本文档覆盖了 React Native 从底层引擎到上层应用、从旧架构到新架构、从基础概念到高级原理的全面内容。建议结合实际项目经验理解各知识点,做到融会贯通。