关于 react-native-advanced
面向 React Native 和 Expo 开发者的进阶技能,整理 TanStack、XState 与 Zustand 生态的原生端模式、集成要点和常见陷阱。
主要功能
- 01React Query 的原生端集成规则
- 02Expo Router 导航与认证模式
- 03FlashList 无限滚动实践
- 04MMKV 持久化与加密存储
- 05动画、手势与推送更新指南
适用场景
- 01配置 AppState 与 NetInfo 查询管理器
- 02为 Expo Router 屏幕预取和刷新数据
- 03构建基于 FlashList 的分页列表
- 04处理动画、通知和 OTA 更新
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trancong12102/ agentskills --skill react-native-advanced安装后在 Claude Code 中输入/react-native-advanced确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
references/animations.mdbottom-sheet.mdexpo-essentials.mdexpo-router.mdlists.mdnotifications.mdreact-query-rn.mdtesting-rn.mdzustand-rn.md详细介绍
React Native 进阶:Expo + TanStack/XState/Zustand 生态
面向基于 TanStack 生态、XState 和 Zustand 构建的应用的 React Native 和 Expo 模式。本技能扩展了 react-advanced(核心跨平台模式)。请先阅读该技能,了解 React Query、XState、Zustand、Zod、TanStack Form 和 TanStack Table 的约定。
RN 架构
与 Web 相比,各库在 React Native 中的对应关系有所不同:
| Web 库 | RN 对应项 | 主要区别 |
|---|---|---|
| TanStack Router | Expo Router | 原生端没有路由加载器,采用基于文件的导航 |
| TanStack Start | — | 原生端没有 SSR/服务端函数 |
| TanStack Virtual | FlashList | 原生视图回收,而非 DOM 虚拟化 |
| localStorage | MMKV | 同步、在原生线程运行,速度快 30 倍 |
| window events | AppState/NetInfo | 需要手动接入焦点/在线管理器 |
| CSS animations | Reanimated | UI 线程 worklet、CSS 过渡(v4) |
| DOM events | Gesture Handler | 手势组合 API、UI 线程回调 |
<img> | expo-image | SDWebImage/Glide、blurhash、磁盘缓存 |
| Web Push API | expo-notifications | FCM/APNs、渠道、后台任务 |
| Service Workers | expo-updates | OTA 更新、分阶段发布、紧急回滚 |
跨平台库(在 Web 和 RN 上使用相同的 API): React Query、XState、Zustand、Zod、TanStack Form、TanStack Table
必需的设置
两项集成必须配置,否则 React Query 的自动重新获取和离线处理在 React Native 中无法正常工作:
- focusManager — 将
AppState接入focusManager.setFocused(),使应用返回前台时refetchOnWindowFocus能够正常工作。若未配置,默认行为会被静默忽略。 - onlineManager — 将
NetInfo接入onlineManager.setOnline(),使查询在网络连接状态变化时暂停/恢复。
在 QueryClientProvider 中的根 _layout.tsx 里,各调用一次这两个钩子。
完整实现请参阅 references/react-query-rn.md。
无路由加载器的数据获取
Expo Router 没有原生路由加载器(数据加载器仅适用于 Web,且仍处于 alpha 阶段)。模式是:在用户交互时预取数据,并在目标屏幕中使用。
规则:
- 在
router.push之前调用prefetchQuery,不要使用await— 等待会导致导航变慢。 - 使用
useFocusEffect(来自expo-router),在屏幕重新获得焦点时使过期数据失效。屏幕会保持挂载,因此导航返回时useEffect不会重新运行。 - 使用
invalidateQueries(遵循staleTime),而不是refetch()(总会重新获取)。
预取和 useRefreshOnFocus 模式请参阅 references/react-query-rn.md。
导航 + 身份验证
Stack.Protected(Expo Router v5+,推荐)
// app/_layout.tsx
export default function RootLayout() {
const session = useAuthStore((s) => s.session)
return (
<Stack>
<Stack.Protected guard={!!session}>
<Stack.Screen name="(tabs)" />
<Stack.Screen name="modal" options={{ presentation: 'modal' }} />
</Stack.Protected>
<Stack.Protected guard={!session}>
<Stack.Screen name="sign-in" />
</Stack.Protected>
</Stack>
)
}
当 session 发生变化时,Expo Router 会自动重定向并清理历史记录。对于复杂的身份验证流程(令牌检查、刷新、错误恢复),使用 XState 管理身份验证状态,并为 guard 属性派生一个布尔值。
XState 身份验证变体和 Zustand 身份验证存储模式请参阅 references/expo-router.md。
列表:FlashList + React Query
FlashList 在 RN 中取代 TanStack Virtual——它使用原生视图复用,而不是基于 DOM 的绝对定位。
规则:
onEndReached中的!isFetchingNextPage守卫至关重要——FlashList 可能会在短时间内多次触发onEndReached,导致重复获取。- 使用
useMemo记忆扁平化后的items数组——data.pages.flatMap()每次渲染都会创建一个新的引用。 - 使用
useCallback稳定handleEndReached。 getNextPageParam必须返回undefined(而不是null),以表示没有下一页。
完整的无限滚动模式、FlashList v2 变更和性能调优,请参阅 references/lists.md。
RN 中的 TanStack Form
TanStack Form 是无头框架——不依赖 DOM,也不需要适配器。与 Web 的主要区别:
TextInput使用onChangeText(直接传入字符串),而不是onChange(事件对象)。 将field.handleChange直接绑定到onChangeText。- 对于数值字段,在调用处进行转换:
onChangeText={(val) => field.handleChange(val === '' ? null : Number(val))}。 - 使用
keyboardShouldPersistTaps="handled"将表单包在ScrollView中——否则,首次点击 Submit 时会关闭键盘,而不会触发按下事件。
使用 MMKV 的 Zustand Persist
MMKV 是同步的,并且运行在原生线程上——不存在异步水合间隙。在模块级别创建 MMKV 实例(绝不要在组件内部创建)。StateStorage 适配器在键不存在时必须返回 null:mmkv.getString(name) ?? null。
对于敏感数据,请使用带有 encryptionKey 的加密 MMKV 实例。有关混合模式(硬件保护密钥 + 加密 MMKV),请参阅 references/expo-essentials.md。
完整的适配器、存储创建、加密存储、水合时序和原生存储模式,请参阅 references/zustand-rn.md。
动画与手势
Reanimated 通过 worklet 在 UI 线程 上运行动画。Gesture Handler 会将触摸事件路由到同一线程。Reanimated 4 新增了 CSS 风格的声明式过渡。
关键规则
- 替换整个值:使用
sv.value = { x: 50 },不要使用sv.value.x = 50(会破坏响应性) - 不要解构:
const { x } = sv.value会创建普通数字,而不是响应式值 - 不要在渲染期间读取:读取共享值属于副作用,违反 React 规则
- 共享值不会触发重新渲染:如果需要 React 做出响应,请维护单独的状态
GestureHandlerRootView必须以style={{ flex: 1 }}包裹应用根节点- Android 模态框需要自己的
GestureHandlerRootView(位于原生根视图之外)
线程处理概要
共享值位于 UI 线程。在 JS 线程上读取 .value 会产生阻塞式桥接调用——绝不要在热点路径中这样做。手势回调和 useAnimatedStyle 在 UI 线程上运行。使用 runOnJS(Reanimated 3)或 scheduleOnRN(Reanimated 4)调用 JS 函数。
声明式与命令式
对于由状态驱动的样式变更(切换颜色、不透明度、尺寸),使用 CSS 过渡(Reanimated 4)。对于挂载/卸载,使用 布局动画(entering/exiting 属性)。对于由手势驱动的动画和命令式动画链,使用 worklet + 共享值。
详细模式、Reanimated 4 API 变更、手势组合和 CSS 过渡,请参阅 references/animations.md。
底部弹出层
@lodev09/react-native-true-sheet——由 UISheetPresentationController(iOS)和 BottomSheetDialog(Android)支持的原生底部弹出层。不依赖 Reanimated。仅支持新架构。
关键规则
- 仅使用命令式控制——使用
ref.present()/dismiss()/resize(),不要使用状态属性。 - 最多使用 3 个停靠位置,按从小到大排序。内容自适应时使用
'auto'。 - 不要将
scrollable与'auto'停靠位置组合使用——两者会冲突。请使用固定停靠位置。 - 不要在工作表内容上使用
flex: 1——这会使高度塌缩为零。请使用flexGrow或固定高度。 - 卸载前务必调用
dismiss()——原生工作表的生命周期会超过 React 组件。 - 可使用标准
TextInput——无需特殊的键盘组件(原生会处理)。 - 标准
ScrollView/FlashList可正常使用,搭配scrollable+nestedScrollEnabled即可——v3 会自动检测(最多深入 2 层)。
完整用法、平台差异和 Expo Router 集成,请参阅 references/bottom-sheet.md。
推送通知
expo-notifications 通过 Expo 的推送服务处理 FCM(Android)和 APNs(iOS)。
SDK 53 重大变更
- Android 推送在 Expo Go 中无法使用——需要开发构建。
- 必须在
app.json的plugins数组中显式配置插件。
关键模式
// Must configure or foreground notifications are silently suppressed
Notifications.setNotificationHandler({
handleNotification: async () => ({
shouldShowBanner: true,
shouldShowList: true,
shouldPlaySound: true,
shouldSetBadge: false,
}),
});
通知规则
- 在 Android 13 及更高版本上,请求权限前必须先创建 Android 通知渠道。
- 通知渠道不可变——创建后无法更改重要性或振动设置。
- 在生产环境中轮询回执——票据只能确认 Expo 已收到请求。
- 必须在模块作用域定义后台任务(不能在组件内部定义)。
有关权限处理、监听器和后台任务,请参阅 references/notifications.md。
OTA 更新
expo-updates 支持通过无线方式更新 JavaScript bundle,无需发布到应用商店。
更新检查模式
import * as Updates from "expo-updates";
// Run after first render, not during startup (can freeze UI on slow network)
const check = await Updates.checkForUpdateAsync();
if (check.isAvailable) {
await Updates.fetchUpdateAsync();
await Updates.reloadAsync();
}
紧急启动检测
if (Updates.isEmergencyLaunch) {
// OTA caused a crash — app rolled back to embedded bundle
// Log immediately to error tracking
}
务必对此进行监控——这是 OTA 更新在生产环境中的崩溃信号。
有关分阶段发布、运行时版本和响应式模式,请参阅 references/expo-essentials.md。
文件组织
结构:app/ 包含路由文件,每个路由段都有一个 _layout.tsx。路由组 (auth)/、(app)/、(tabs)/ 用于组织路由,不影响 URL。功能代码放在 app/ 之外:queries/(queryOptions 对象,而非钩子)、mutations/、machines/(纯 TS,不导入 React)、stores/(Zustand + MMKV 持久化)、hooks/、components/。
常见陷阱
-
未设置 focusManager/onlineManager — 未手动接入 AppState 和 NetInfo 时,
refetchOnWindowFocus和离线暂停都不会生效。 -
在
router.push前等待prefetchQuery完成 — 会让导航显得缓慢。无需等待,直接触发预取,让 React Query 缓存为目标屏幕提供数据。 -
使用
useGlobalSearchParams而不是useLocalSearchParams— 全局参数会在每次导航事件时触发重新渲染。始终优先使用局部参数。 -
用
useEffect在屏幕获得焦点时重新获取数据 — 导航返回时,useEffect不会重新运行(屏幕仍处于挂载状态)。请使用expo-router中的useFocusEffect。 -
getNextPageParam返回null— 必须返回undefined才能表示没有下一页。将 API 返回的 null 转换为:lastPage.nextCursor ?? undefined。 -
FlashList v2 需要 New Architecture — v2 是针对 Fabric 从头重写的版本。
estimatedItemSize已弃用(会被忽略)。overrideItemLayout仅支持span。 -
MMKV 的
getString返回undefined— Zustand 的StateStorage.getItem在键不存在时必须返回null。始终进行转换:?? null。 -
在组件内部创建 MMKV 实例 — 每次渲染都会创建新实例。请在模块级别声明。
-
表单 ScrollView 未设置
keyboardShouldPersistTaps— 第一次点击会关闭键盘,而不是按下 Submit 按钮。 -
在移动设备上使用默认的
retry: 3— 网络不稳定时,指数退避的 3 次重试可能耗时 30 秒以上。对于时间敏感的 UI,可考虑设置retry: 1。 -
修改共享值的属性 —
sv.value.x = 50会破坏响应性。必须替换整个值:sv.value = { x: 50, y: 0 }。 -
GestureHandlerRootView 缺少
flex: 1— 包装器的高度为零,手势看起来完全不起作用。 -
TrueSheet 打开时将其卸载 — 原生表单不会自动关闭。从树中移除之前,务必调用
dismiss()。 -
在 TrueSheet 中将
autodetent 与scrollable搭配使用 — 自动调整尺寸与滚动固定会冲突。scrollable={true}时请使用固定 detents。 -
未设置
setNotificationHandler— 所有前台通知都会被静默屏蔽,且不会报错。 -
慢速网络下调用
checkForUpdateAsync— 可能导致 Android UI 冻结数分钟。请在首次渲染后运行,并设置手动超时。 -
在 FlashList 中使用 expo-image 时未设置
recyclingKey— 如果不设置,回收的单元格会闪现上一个项目的图片。 -
expo-secure-store 生物识别失效 — 生物识别信息变更时,设置了
requireAuthentication的密钥将永久无法读取。除清除应用数据外,没有恢复方法。
参考文件
| 文件 | 阅读时机 |
|---|---|
references/react-query-rn.md | 焦点/在线管理器、缓存持久化、预取 |
references/expo-router.md | 类型化路由、布局、模态框、身份验证、搜索参数 |
references/lists.md | FlashList + React Query、无限滚动、性能 |
references/zustand-rn.md | MMKV 持久化适配器、加密、水合模式 |
references/testing-rn.md | RNTL、测试 Query/Router/Form/XState、RN 中的 MSW |
references/animations.md | Reanimated、Gesture Handler 模式与注意事项 |
references/bottom-sheet.md | react-native-true-sheet 设置、detents、Expo Router |
references/notifications.md | expo-notifications 权限、监听器、后台 |
references/expo-essentials.md | expo-image、expo-secure-store、expo-haptics、expo-updates |