Skip to content
常见陷阱与最佳实践

常见陷阱与最佳实践

这一章是整个手册的收口:把散在 00~11 章的坑汇总成一张速查表,再给出一份 2026 年视角的"旧教程纠偏清单"、最佳实践和上线自检。写 RN 应用出事前,先来这里对一遍。

基线:React Native 0.88 / Expo SDK 58 / TypeScript。版本号均为 2026-10 实测(npm registry、官方仓库、Expo changelog),不确定的写法在正文已标注"以当期文档为准"。


一、陷阱汇总表

环境与版本

陷阱 症状 解法 详见
还在用 Node 20 CLI 报错、构建诡异失败 RN 0.87/0.88 只认 ^22.13 || ^24.3 || >=26;Expo 58 同口径且不支持 23/25 奇数线 00-安装与快速开始.md
被模板 engines 误导 装了 22.11/22.12 bare 模板 package.json 写 >=22.11,但 react-native core engines 是 ^22.13.0——按高的那个取 00
react 与 react-native 没配对 启动即崩、Invalid hook call 0.88 必须精确配 React 19.3.0;Expo 项目一律 npx expo install,别用 npm i 装 RN 生态包 00、01
按 2023 年教程开旧架构开关 newArchEnabled=false 无效甚至报错 0.82 起旧架构已整体移除,没有回头路;报错方向是查库的新架构兼容性 01、09
用旧教程的初始化命令 react-native init 已死 bare 用 npx @react-native-community/cli@latest init;官方推荐路径其实是 npx create-expo-app 00

组件与列表

陷阱 症状 解法 详见
裸字符串放进 View 红屏/运行时报错 一切文字必须包在 <Text> 里 02
FlatList extraData 传可变数组 选中态不刷新,且无任何报错 extraData 走引用比较;push 后引用不变等于没更新,改用不可变更新(setState 新数组) 02
ListHeaderComponent={<SearchBar />} 行内 JSX 每次父渲染整块 header 重建、列表跳动 header 元素 useMemo 固定引用,或直接传组件 02
ScrollView 当长列表用 内存随数据量线性膨胀、滚动掉帧 超过十几行就换 FlatList;虚拟化是底线 02
Modal 忘配 onRequestClose iOS 一切正常,Android 用户被返回键困死 必传;这是平台不对称坑的头号代表 02
用 RN 自带 SafeAreaView 仅 iOS 生效、Android 贴刘海 官方已标注弃用,且 bare 0.88 模板直接预装 react-native-safe-area-context——用它 02、03
用 Touchable 系列写新代码 老 API、行为差异多 统一 Pressable;onPress 是抬起、onPressIn 是按下,别混 02、06
Android 上 removeClippedSubviews 期望过高 快速滚动白屏或无效 它只对"内容远大于视口且高度固定"场景有收益,先做好 getItemLayout 02

样式与布局

陷阱 症状 解法 详见
按 Web 心智设 flexDirection 布局全变竖的 RN 默认 column,Web 默认 row 03
等 CSS 继承 子 View 文字样式全丢 文本样式只沿 <Text> 嵌套链传递 03
Dimensions.get 算一次存全局 旋转/分屏后尺寸过期 用 useWindowDimensions,跟着屏幕走 03
iOS 阴影配方原样搬 Android Android 上阴影消失或一团黑 Android 用 elevation 近似;阴影双写是常态 03
edge-to-edge 时代还指望状态栏特例 Android 15+ 强制内容延伸到系统栏后 0.86 是 RN 的适配主版本、0.88 模板默认 edgeToEdgeEnabled=true;安全区交给 react-native-safe-area-context 03、04
用 onLayout 同步拿尺寸 首帧拿到 0 或延迟值 onLayout 是异步事件且只在挂载时触发一次;测量用 ref.measureInWindow 并做好回调 03

导航与路由

陷阱 症状 解法 详见
以为屏内切屏会重新挂载 navigate 回已有屏,useEffect 不跑 同屏实例复用;“切回就刷新"用 useFocusEffect,“参数变就刷新"把 params 进依赖数组 04
列表→详情传整个对象 参数膨胀、栈内存吃紧、深链对不上 传 id,详情页自己取数(和 05 的 Query 缓存配合) 04
认证流靠 navigate 后清栈 登出后返回键还能回用户页 条件渲染 screen 树 + reset,从根上不让受保护屏入栈 04
Android 硬件返回键裸 Modal/自绘弹层 返回键直接退 App 或无响应 BackHandler 注册并记得卸载时移除监听 04

网络与数据

陷阱 症状 解法 详见
onEndReachedThreshold 不设防 一次滑动连发多页重复请求 默认 0.5(距底半屏)就会触发;分页必须 hasNextPage && !isFetchingNextPage 双守卫 05
下拉刷新与分页守卫混用 下拉时分页卡死(或反之) isRefetching 归下拉、isFetchingNextPage 归分页,别互相顶替 05
fetch 不配超时 弱网下请求无限悬挂 RN 的 fetch 没有原生超时参数;AbortController + setTimeout 自己包 05
组件卸载后 setState 警告(旧)/静默内存驻留(新) effect 里返回 cleanup,AbortController.abort() 一并取消 05
FormData 按浏览器习惯传 File 上传拿不到文件 RN 的约定是 { uri, name, type } 对象形态 05
Android 模拟器里写 localhost 127.0.0.1 连的是模拟器自己 10.0.2.2;真机开发用局域网 IP 或 Expo tunnel 05

存储

陷阱 症状 解法 详见
几百 KB 的 JSON 塞 AsyncStorage JS 线程被解析卡住 大对象拆分或上 SQLite;KV 只放配置级数据 08
MMKV 同 key"push 一条” 整表被静默覆盖 MMKV 的 set 是纯覆盖写,没有追加语义;读-改-写走事务化函数 08
可再生缓存写进 documents 目录 用户 iCloud 被备份爆仓、审核风险 cache 目录不参与备份;documents 会被云备份 08
token 存 AsyncStorage 明文落盘 安全存储走 Keychain/Keystore:expo-secure-store(注意单值大小量级限制,别塞大 JSON) 08
按 v3 教程初始化 MMKV 找不到 createMMKVStorage v4 工厂统一为 createMMKV(),且需要 react-native-nitro-modules peer 依赖(registry 实测) 08

动画与手势

陷阱 症状 解法 详见
worklet 里闭包捕获普通 JS 值/函数 UI 线程运行时直接抛错或行为诡异 跨线显式 runOnJS;shared value 是唯一常规通道 07
动画里 setState 每帧一次 JS 渲染,60fps 变 20fps 状态变更走 shared value,React 渲染不参与动画 07
width/height 动画想走原生驱动 掉帧或报错 只有 transform 三件套(translate/scale/rotate)与 opacity 类能上原生驱动;尺寸动画改用 Reanimated layout animation 或重排方案 07
手势嵌套 ScrollView 冲突 拖拽抢不过滚动 Gesture Handler 的激活条件与 simultaneousHandlers 显式声明;GestureHandlerRootView 要包在根上 07
jest 里 import reanimated 测试直接炸 需要按库文档注入 mock(形态随版本变过,以当期测试章节为准) 07、10

表单与输入

陷阱 症状 解法 详见
受控 TextInput 撞中文输入法 Android 拼音候选期间回灌、候选闪烁/删字 RN 无 composition 事件,平台级限制;长中文文本改非受控或提交时再校验 06
同一输入框动态切 keyboardType 键盘闪烁、焦点丢失 需要切类型就拆成两个输入框(如手机号/验证码) 06
KeyboardAvoidingView 平台一刀切 iOS 键盘弹出错位、Android 双重补偿 iOS behavior="padding";Android 优先 windowSoftInputMode="resize" 并跳过 KAV 06
以为 multiline 有 autoGrow prop 不存在、不生效 官方源码无此属性;onContentSizeChange 夹高度自实现 06
密码框忘配 textContentType 密码管理器/自动填充失效、审核扣分 值是 password / newPassword(不是 passwordNew,旧文章写错的那批);配合 autoCapitalize="none" 06
用 InputAccessoryView 做键盘工具条 0.88 已弃用,红字警告 迁移到 Keyboard Avoiding + 自绘悬浮条或社区方案 06
Android 13+ 只申请 READ_EXTERNAL_STORAGE 拿不到相册、权限弹窗缺失 细分到 READ_MEDIA_IMAGES/VIDEO;iOS 的 Info.plist 用途文案是审核拒项 06

原生模块与架构

陷阱 症状 解法 详见
TurboModule 名字三处不一致 运行期拿到 null/抛异常,没有编译错误 JS getEnforcing 参数、Android getName()、iOS 导出宏里的名字必须逐字相同 09
改完原生代码等 Fast Refresh 刷新八百遍行为不变 原生改动必须全量重建 run:ios/run:android 09
链接问题先猜注册错 浪费半天 先 npx react-native config 看发现链路,再 TurboModuleRegistry.get 探测可见性,最后进断点 09
存量库不体检就升 RN 大版 某个桥时代库在 0.82+ 集体爆 升级前逐个核对新架构兼容状态;0.88 的桥兼容层开关是"未来的刀”,别拿它当长期饭票 09

调试与测试

陷阱 症状 解法 详见
教程让你开 Chrome remote debugging / 装 Flipper 找不到入口/文档对不上 0.76 起统一用 React Native DevTools,Metro 终端按 d 10
jest 没配 transformIgnorePatterns 一跑测试满屏 SyntaxError(flow/TS 语法) RN 生态包大多发未编译源码,白名单放行 react-native 相关包是标准配方 10
release 包崩溃栈全是 anonymous 线上事故无法定位 Hermes 字节码需要 source map/dSYM 符号化;构建产物归档 mapping,上报服务(Sentry/Expo Observe)收口 10、11
@react-native/jest-preset 与 RN 版本错配 preset 行为对不上运行时 0.85 起独立发版,跟着 RN 主版本一起升 10

构建与发布

陷阱 症状 解法 详见
keystore 只存一台机器/进了 git 双输:要么丢=更新断供,要么泄露 双备份、双人可及、绝不进仓库;Play App Signing 也救不了签名密钥丢失 11
OTA 热更当万能 原生改动推出去当场炸 EAS Update 只送 JS/资源;runtimeVersion policy 选定就别中途换(appVersion 会把新 JS 推给不同原生的老壳) 11
Android versionCode 忘了加 商店拒收/覆盖上传失败 每次必增,进发版 checklist 11
R8/混淆问题只在 release 复现 debug 一切正常,线上类找不到 release 构建必须真机全链路冒烟,mapping.txt 归档 11
提审卡在政策与权限文案 反复被拒浪费一周 相机/相册/定位用途文案提前写好;审核窗口避开周五之后 11

二、旧教程纠偏清单(2026 版)

网上教程与模型记忆大量停留在 2022~2024,下表的"旧写法"今天照抄会直接错。这一节同样适合排查"AI 生成的 RN 代码"时对照。

旧教程写法 现状 依据
react-native init / react-native-community/cli 全局安装 bare 用 npx @react-native-community/cli@latest init;官方推荐其实是 Expo 00
Android 阴影 shadowOpacity/shadowRadius 双端通用 Android 走 elevation(或新版本 boxShadow 路线),iOS 走 shadow* 03
TouchableHighlight/TouchableOpacity 统一 Pressable 02
RN 自带 SafeAreaView 已标注弃用;用 react-native-safe-area-context(0.88 模板预装) 02
从 RN 导入 Picker / CheckBox 早已移出核心到社区包 06
InputAccessoryView 做键盘辅助条 RN 0.88 弃用(官方 changelog 实测) 06
import 'react-native/Libraries/Core/InitializeCore' RN 0.88 弃用,改 react-native/setup-env(官方 changelog 实测) 01
import Text from 'react-native/Libraries/Components/Text/Text' 深导入 0.87 起 Strict TypeScript API 默认,深导入是类型错误 01、11
newArchEnabled: false 回退旧架构 0.82 起旧架构不存在,flag 无意义 01
Flipper 做调试中枢 0.76 起模板移除,React Native DevTools(按 d)接班 10
“iOS 需要自己编译 Hermes 源码” 0.84 起 iOS 默认预编译 RN 框架二进制,pod install 分钟级 00
createMMKVStorage() 初始化 MMKV v4 统一 createMMKV(),并新增 react-native-nitro-modules peer(registry 实测) 08
textContentType: 'passwordNew' 正确值是 newPassword(源码实测) 06
multiline + 期望存在 autoGrow TextInput 无此属性(源码实测);用 onContentSizeChange 夹高 06
Node 18/20 够用 RN 0.87+ 的 core engines 已踢出 Node 20 00

三、最佳实践清单

项目与依赖

  • 新项目默认走 Expo(官方推荐),对原生工程强控制的场景再选 bare——两条路的退路都是通的:Expo 可 prebuild 出完整原生工程,bare 也可随时接入 Expo 生态的单包。
  • Node 版本锁进仓库(volta/.nvmrc),CI、开发机、同事三处一致;react/react-native 版本配对表贴在 README。
  • npx expo install(Expo)或对照官方模板(bare)管理依赖版本,避免"npm i 装出 peer 漂移"。
  • 第三方库当成本金:升级前核对新架构兼容与近期维护活跃度;无新架构适配的库当作替换对象,不是等待对象。
  • 深导入一律不用,0.87 起它就是类型错误;组件、hooks 全从 react-native 顶层取。

性能

  • 首屏预算倒推:bundle 体积(延迟加载非首屏代码)、启动链路(Hermes 字节码是默认优势,别再关)、列表虚拟化。
  • 列表三板斧:稳定的 keyExtractor、renderItem 提到组件外或用 useCallback、行内容不可变更新;高频滚动列表再考虑社区虚拟化方案(如 FlashList)。
  • 动画铁律:能进原生驱动就进原生驱动;手势驱动动画交给 Reanimated 的 UI 线程运行时,绝不在动画里 setState。
  • 图片是内存大户:尺寸按显示需求下发(服务端出图或 CDN 参数),列表图片配 resizeMode 与稳定 key。
  • 别信"感觉不卡":DevTools Profiler + 系统级工具(Android Studio Profiler / Xcode Instruments)看证据。

稳定性与安全

  • 每个 effect 的 cleanup 当作军规:监听器(Keyboard/Dimensions/AppState)、abort、定时器——泄漏三兄弟。
  • 请求层集中做:超时、token 注入、错误归一化;业务代码不裸写 fetch。
  • token 只进系统级安全存储(Keychain/Keystore),不进出 AsyncStorage/MMKV 明文档。
  • 崩溃上报收口在构建里:source map/mapping.txt/dSYM 与构建产物一一对应归档;Expo 项目 Observe 已覆盖原生崩溃上报(AppMetrics 的后继)。
  • 权限文案当产品文案写:Android 13+ 媒体细分权限、iOS Info.plist 用途描述,都是审核拒项高发区。

协作与发布

  • versionCode 只增不减、发布分支打 tag、runtimeVersion policy 一个项目定一次。
  • keystore 双备份 + 双人可及;签名事故没有第二次机会。
  • 发版排期把"等审核"和"48 小时观察窗"占正式格子;OTA 全量切换只在有人值守时段。
  • 升级节奏:追 Expo SDK 大版本比追 RN 双月版现实(对应关系见下表);RN 是 0.88 这种"无破坏性小步快跑"节奏,跨 2~3 个版本一次性升,配合官方 upgrade helper 思路逐文件核 diff。

四、版本配对速查(实测)

RN 与 React 的配对(npm peerDependencies 实测):

RN React Node(core engines) Expo SDK
0.78 / 0.79 ^19.0.0 >=18 —
0.80 ^19.1.0 >=18 —
0.81 ^19.1.0 >=20.19.4 54
0.82 ^19.1.1 >=20.19.4 —
0.83 ^19.2.0 >=20.19.4 55
0.84 ^19.2.3 >=20.19.4 —
0.85 ^19.2.3 ^20.19.4 || ^22.13 || ^24.3 || >=25 56
0.86 ^19.2.3 同上 57
0.87 ^19.2.3 ^22.13 || ^24.3 || >=26 —
0.88 ^19.3.0 ^22.13 || ^24.3 || >=26 58

(React/Node 两列均为 npm registry 的 peerDependencies.react 与 engines.node 逐项实测,2026-10-20。)

要点:RN 约 2 个月一版;Expo SDK 挑版本上车(54→0.81、55→0.83、56→0.85、57→0.86、58→0.88),中间 RN 版本由 Expo 吸收。生产项目按 Expo SDK 线走,bare 项目按"季度追一次 RN minor"走。


五、上线自检清单

发版前逐项打钩:

  • Node/RN/React 版本三者配对,npx expo install --check(Expo)或 lockfile 审查(bare)通过
  • 所有依赖库确认新架构兼容(0.82+ 没有旧架构退路)
  • 深导入清零(0.87 Strict TS API 下编译干净)
  • Android targetSdk/compileSdk 对齐当期商店政策与模板值(0.88 模板实测 targetSdk 36、compileSdk 37)
  • edge-to-edge 双端真机过一遍:状态栏/导航栏内容不被遮、系统栏图标可读
  • 深色模式全页巡览(useColorScheme 路径 + 图片双资源)
  • 平板/折叠屏/大字体无障碍各截图一次(accessibility 是审核项)
  • 列表页:空态/加载/错误/分页守卫四态齐;长列表 60fps 有 profiler 证据
  • 表单页:中文输入法回归测试、键盘遮挡双平台截图、防重复提交生效
  • 网络:飞行模式/弱网下无无限悬挂(超时封装生效)、下拉刷新与分页互不锁死
  • 敏感数据不在明文 KV;权限文案双端齐全
  • release 构建真机冒烟(R8/Hermes 字节码只在 release 暴露问题)
  • 崩溃符号化链路演练:拿一条 release 崩溃能还原到行
  • keystore/证书/EAS 凭证就位,runtimeVersion policy 与上次发布一致
  • OTA 边界确认:本次版本有无原生改动,update channel 目标正确
  • 48 小时观察窗排期落格,回滚触发阈值写进发布记录

六、自测题

  1. 为什么 FlatList 的 extraData={arr} 配 arr.push(x) 不刷新?正确的最小修法是什么?(02)
  2. RN 0.82 之后还能不能"退回旧架构"?如果某个库只有桥时代实现,你的三条出路是什么?(01、09)
  3. 分页列表为什么必须 hasNextPage && !isFetchingNextPage 双守卫?只用其中一个分别会怎样?(05)
  4. EAS Update 推了一次含原生依赖升级的包,会发生什么?怎么用流程杜绝?(11)
  5. 用户反馈"拼音打字时输入框闪烁删字",这是 bug 吗?你有哪些现实缓解手段?(06)
  6. 同一个 TurboModule 在 Android 能拿到、iOS 是 null,排查顺序是什么?(09)
  7. 写一条旧→新对照:react-native init、Flipper、newArchEnabled=false、createMMKVStorage 分别对应今天什么?(本章二节)

实战建议

把这本手册当"版本敏感"文档读:凡涉及具体版本号,以本文件第四节的实测表为准;凡涉及三方库 API(Reanimated、Gesture Handler、MMKV、Expo 各包),它们的版本线各自独立,动手前查当期官方文档的对应章节。RN 这两年的变化密度很高(旧架构移除、Hermes V1、Strict TS、edge-to-edge),2023 年以前的教程与代码生成结果只能当思路参考,不能当事实来源——这正是本手册把"纠偏清单"放在收口的原因。