Communitygithub.com

react-native-patterns

React Native 和 Expo 应用模式 — Expo Router 导航、状态分离(服务端/客户端/路由/表单)、TanStack Query 数据获取与 Zod、高性能列表、NativeWind/StyleSheet 样式、原生 API 和安全存储。在构建或编辑 React Native / Expo 屏幕、组件、导航或数据层时使用。

react-native-patterns 是什么?

该技能为使用 Expo 构建生产级 React Native 应用提供了一套实用模式,涵盖导航、状态管理、数据获取、列表渲染、样式和原生 API 接入。它强调文件路由(Expo Router)、状态分离(服务器缓存、客户端存储、路由参数、表单状态各司其职),并推荐使用 TanStack Query 进行数据获取,配合 Zod 进行运行时验证。技能还提供了虚拟化列表(FlatList/FlashList)、样式选择(NativeWind 或 StyleSheet)以及原生 API 封装(如 expo-location)的最佳实践。它特别适合在构建或修改 Expo SDK 项目中的屏幕、组件、导航或数据层时使用,并明确区分了 Web/React-DOM 模式与 React Native 原生模式。该技能支持所有主流平台(iOS、Android、Web),并提供了丰富的代码示例和反模式指引。

兼容平台~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/tree/main/skills/react-native-patterns

在你喜欢的 AI 中提问

打开一个已预加载此 Agent Skill 的新对话。

文档

React Native / Expo 模式

构建生产级 Expo React Native 应用的实用模式。涵盖导航、状态、数据获取、列表、样式和原生 API。与 rules/react-native/ 规则集配合使用:规则说明要强制执行什么,本技能展示如何实现。

下面列出的库(NativeWind、Zustand/Jotai、TanStack Query)是常见且成熟的选择,仅作示意 — 模式比具体包更重要,任何等效方案均可。使用 Zod 进行验证,以与 ECC 现有的 typescript/ 规则保持一致。

这些模式假设在新架构(近期 Expo SDK 默认可选,SDK 55+ 后为强制项)上使用托管 Expo 工作流(Expo Router、EAS、expo-* 模块)。它们不假设浏览器 DOM — React Native 没有 <div>、没有 URL 栏,也没有 Web 数据获取的默认行为。

何时激活

在以下情况下使用此技能:

  • 构建或编辑 React Native / Expo 屏幕、组件或导航
  • 使用 Expo Router(基于文件系统的 app/ 目录)设置路由
  • 决定状态归属(服务器缓存 vs 客户端存储 vs 路由参数 vs 表单)
  • 使用 TanStack Query 进行数据获取,并用 Zod 验证响应
  • 渲染长列表或重型列表
  • 选择或应用样式方案(NativeWind 或 StyleSheet)
  • 访问原生设备 API(相机、位置、通知)或安全存储
  • 审查移动端特定问题的 RN 代码

不要在此使用 Web/React-DOM 模式 — URL 即状态、<div> 以及浏览器专用的 SWR 不适用于 React Native。

核心概念

项目结构(Expo Router)

基于文件系统的路由位于 app/ 下。保持路由文件精简:它们读取并验证参数,然后委托给位于 components/ 或 features/ 中的屏幕组件。

app/
  _layout.tsx          # 根堆栈
  (tabs)/
    _layout.tsx        # 标签导航器
    index.tsx          # 首页
  user/[id].tsx        # 动态路由
components/
features/
  user/UserProfile.tsx

导航:验证路由参数

深度链接和动态路由传递不可信字符串。使用 Zod 在使用前进行验证。

// app/user/[id].tsx
import { useLocalSearchParams, router } from 'expo-router'
import { z } from 'zod'
import { UserProfile } from '@/features/user/UserProfile'

const Params = z.object({ id: z.string().uuid() })

export default function UserRoute() {
  const parsed = Params.safeParse(useLocalSearchParams())
  if (!parsed.success) {
    router.replace('/not-found')
    return null
  }
  return <UserProfile userId={parsed.data.id} />
}

状态:保持关注点分离

不要将服务器数据复制到客户端存储中。每个关注点都有自己的归属。

关注点常用选择
服务器状态(远程数据)服务器缓存库(TanStack Query、SWR)
客户端/UI 状态轻量级存储(Zustand、Jotai)或 Context
路由/导航状态Expo Router 参数
表单状态表单库(例如 React Hook Form)+ 模式验证
密钥/令牌expo-secure-store
非机密持久化AsyncStorage / MMKV

优先使用本地 useState,直到状态确实需要共享。

数据获取:缓存库 + Zod

使用服务器缓存库(TanStack Query、SWR)替代 useEffect 中的 fetch。在边界处进行验证,并从模式中推断类型。显式处理加载、错误和空状态。(示例使用 TanStack Query。)

import { useQuery, useMutation, useQueryClient } from '@tanstack/react-query'
import { z } from 'zod'

const User = z.object({ id: z.string(), email: z.string().email() })
type User = z.infer<typeof User>

export function useUser(id: string) {
  return useQuery({
    queryKey: ['user', id],
    queryFn: async (): Promise<User> => User.parse(await api.getUser(id)),
  })
}

export function useUpdateEmail(id: string) {
  const qc = useQueryClient()
  return useMutation({
    mutationFn: (email: string) => api.updateEmail(id, email),
    onSuccess: () => qc.invalidateQueries({ queryKey: ['user', id] }),
  })
}

列表:虚拟化,绝不在 ScrollView 中映射大型数组

import { FlatList } from 'react-native'

<FlatList
  data={items}
  keyExtractor={(item) => item.id}
  renderItem={renderItem}          // 已记忆化
  initialNumToRender={10}
  windowSize={5}
/>

对于大型或异构列表,使用 FlashList(Shopify)。

样式:选择一种方案

StyleSheet.create() 是框架原生的选项;工具类库(例如 NativeWind)是常见的替代方案。选择一种并保持一致。绝不在热路径上的 JSX 中内联构建样式对象。

// NativeWind
<View className="p-4 rounded-2xl bg-white">
  <Text className="text-base font-semibold">Hello</Text>
</View>

// StyleSheet
const styles = StyleSheet.create({ card: { padding: 16, borderRadius: 16, backgroundColor: '#fff' } })
<View style={styles.card}>...</View>

原生 API:封装在 hooks 中,清理副作用

将 Expo SDK 调用和订阅放在 use* hooks 内部,而不是 JSX 中。始终进行清理。

import { useEffect, useState } from 'react'
import * as Location from 'expo-location'

type LocationState =
  | { status: 'loading' }
  | { status: 'denied' }
  | { status: 'granted'; coords: Location.LocationObjectCoords }

export function useCurrentLocation() {
  // 跟踪状态,而不仅仅是坐标——这样 UI 可以区分"仍在加载"和"权限被拒绝",并显示可操作的消息。
  const [state, setState] = useState<LocationState>({ status: 'loading' })

  useEffect(() => {
    let active = true
    ;(async () => {
      const { status } = await Location.requestForegroundPermissionsAsync()
      if (status !== 'granted') {
        if (active) setState({ status: 'denied' })
        return
      }
      const pos = await Location.getCurrentPositionAsync({})
      if (active) setState({ status: 'granted', coords: pos.coords })
    })()
    return () => { active = false }   // 组件卸载后忽略过时结果
  }, [])

  return state
}

令牌安全存储

import * as SecureStore from 'expo-secure-store'

await SecureStore.setItemAsync('auth_token', token)   // Keychain / Keystore
const token = await SecureStore.getItemAsync('auth_token')

代码示例

完整屏幕:路由 → 查询 → 列表 → 状态

// app/(tabs)/orders.tsx
import { memo, useCallback } from 'react'
import { FlatList, Text, View } from 'react-native'
import { useQuery } from '@tanstack/react-query'
import { z } from 'zod'

const OrderSchema = z.object({ id: z.string(), total: z.number(), status: z.string() })
const OrdersSchema = z.array(OrderSchema)
type Order = z.infer<typeof OrderSchema>

function useOrders() {
  return useQuery({
    queryKey: ['orders'],
    queryFn: async () => OrdersSchema.parse(await api.listOrders()),
  })
}

// 已记忆化,使其引用在渲染间保持稳定(参见列表指南)。
const OrderRow = memo(function OrderRow({ item }: { item: Order }) {
  return (
    <View className="px-4 py-3 border-b border-neutral-200">
      <Text className="font-medium">#{item.id}</Text>
      <Text className="text-neutral-500">{item.status} · ${item.total}</Text>
    </View>
  )
})

export default function OrdersScreen() {
  const { data, isLoading, isError, refetch, isRefetching } = useOrders()
  const renderItem = useCallback(({ item }: { item: Order }) => <OrderRow item={item} />, [])

  if (isLoading) return <Centered><Text>加载中…</Text></Centered>
  if (isError) return <Centered><Text accessibilityRole="alert">无法加载订单。</Text></Centered>
  if (!data?.length) return <Centered><Text>暂无订单。</Text></Centered>

  return (
    <FlatList
      data={data}
      keyExtractor={(o) => o.id}
      onRefresh={refetch}
      refreshing={isRefetching}
      renderItem={renderItem}
    />
  )
}

表单:React Hook Form + Zod 解析器

import { useForm, Controller } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { z } from 'zod'
import { TextInput, Button, Text } from 'react-native'

const Schema = z.object({ email: z.string().email('无效的电子邮件') })
type FormValues = z.infer<typeof Schema>

export function EmailForm({ onSubmit }: { onSubmit: (v: FormValues) => void }) {
  const { control, handleSubmit, formState: { errors } } = useForm<FormValues>({
    resolver: zodResolver(Schema),
    defaultValues: { email: '' },
  })

  return (
    <>
      <Controller
        control={control}
        name="email"
        render={({ field: { value, onChange, onBlur } }) => (
          <TextInput
            value={value}
            onChangeText={onChange}
            onBlur={onBlur}
            autoCapitalize="none"
            keyboardType="email-address"
            accessibilityLabel="电子邮件地址"
          />
        )}
      />
      {errors.email && <Text accessibilityRole="alert">{errors.email.message}</Text>}
      <Button title="保存" onPress={handleSubmit(onSubmit)} />
    </>
  )
}

反模式

// 错误:在 ScrollView 中映射大型数组(无虚拟化、卡顿、高内存)
<ScrollView>{items.map((i) => <Row key={i.id} item={i} />)}</ScrollView>
// 正确:FlatList / FlashList

// 错误:将服务器数据复制到客户端存储(两个数据源、过时数据)
const useStore = create((set) => ({ users: [], setUsers: (u) => set({ users: u }) }))
useEffect(() => { getUsers().then(setUsers) }, [])
// 正确:useQuery 拥有服务器状态;按需派生

// 错误:令牌存储在 AsyncStorage 中(未加密)
await AsyncStorage.setItem('auth_token', token)
// 正确:expo-secure-store

// 错误:信任深度链接参数
const { id } = useLocalSearchParams(); fetchUser(id)
// 正确:使用前用 Zod 验证

// 错误:在热路径上每次渲染都重新创建内联样式对象
<View style={{ padding: 16, backgroundColor: '#fff' }} />
// 正确:在模块作用域使用 StyleSheet.create,或使用 NativeWind className

// 错误:将真实密钥打包在 bundle 中
const STRIPE_SECRET = 'sk_live_...'
// 正确:将特权调用保留在服务端;仅提供受后端规则保护的公钥

最佳实践

  • 保持路由文件精简;将逻辑放在屏幕组件和 use* hooks 中。
  • 使用 Zod 验证每个外部输入(API 响应、路由参数、推送负载)。
  • 让 TanStack Query 拥有服务器状态;保持客户端存储小巧。
  • 始终渲染加载、错误和空状态 — 绝不仅仅是一个没有后备方案的加载指示器。
  • 虚拟化列表;记忆化 renderItem;提供稳定的 keyExtractor。
  • 使用 react-native-reanimated 进行动画(UI 线程);避免在 JS 线程上进行繁重工作。
  • 将令牌存储在 expo-secure-store 中;绝不相信客户端进行授权。
  • 从一开始就尊重安全区域、动态类型和可访问性角色/标签。
  • 在发布前确认每个原生依赖项的新架构兼容性。

相关技能

  • frontend-patterns — React/Next.js(Web)模式;对于共享的 React 概念很有用,但特定于 DOM。
  • coding-standards — 适用于 RN 代码的 TypeScript/JavaScript 惯用法。
  • tdd-workflow、e2e-testing — 测试流程(使用 Jest + React Native Testing Library、Maestro/Detox 进行 RN 测试)。
  • security-review — 补充上述 RN bundle/密钥指南的通用安全检查清单。

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

相关技能