Communitygithub.com

react-native-patterns

React Native 및 Expo 앱 패턴 — Expo Router 네비게이션, 상태 분리(서버/클라이언트/라우트/폼), Zod를 이용한 TanStack Query 데이터 페칭, 성능 최적화 리스트, NativeWind/StyleSheet 스타일링, 네이티브 API, 보안 저장소. React Native / Expo 화면, 컴포넌트, 네비게이션 또는 데이터 레이어를 구축하거나 편집할 때 사용합니다.

react-native-patterns란 무엇인가요?

이 스킬은 Expo 기반의 프로덕션 React Native 애플리케이션을 구축하기 위한 실용적인 패턴을 제공합니다. Expo Router를 사용한 파일 기반 라우팅에서 라우트 파라미터의 Zod 검증, 서버 상태와 클라이언트 상태의 명확한 분리, TanStack Query와 Zod를 결합한 타입 안전한 데이터 페칭, FlatList/FlashList를 활용한 가상화 리스트, NativeWind 또는 StyleSheet를 이용한 일관된 스타일링, 그리고 expo-secure-store를 사용한 보안 토큰 저장까지 모바일 앱 개발의 핵심 영역을 다룹니다. 이 스킬은 web/React-DOM 패턴이 아닌 React Native의 고유한 특성(div 부재, URL 바 부재, 웹 데이터 페칭 기본값 부재)을 존중하며, managed Expo 워크플로우와 New Architecture를 가정합니다. 주요 안티 패턴(ScrollView에서 큰 배열 매핑, 서버 데이터를 클라이언트 저장소에 복사, AsyncStorage에 토큰 저장 등)을 명시하고 올바른 대안을 제시하여 실수를 방지하고 모범 사례를 학습하도록 돕습니다.

지원 대상~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/react-native-patterns

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

React Native / Expo 패턴

Expo로 프로덕션 React Native 앱을 구축하기 위한 실용적인 패턴입니다. 네비게이션, 상태, 데이터 페칭, 리스트, 스타일링 및 네이티브 API를 다룹니다. rules/react-native/ 규칙 세트와 함께 사용됩니다. 규칙은 무엇을 강제할지를 말하고, 이 스킬은 어떻게 할지를 보여줍니다.

아래에 언급된 라이브러리(NativeWind, Zustand/Jotai, TanStack Query)는 예시로 보여주기 위한 일반적이고 잘 정립된 옵션입니다. 패턴이 특정 패키지보다 더 중요하며, 동등한 기능을 하는 어떤 라이브러리든 사용 가능합니다. Zod는 ECC의 기존 typescript/ 규칙과의 일관성을 위해 검증에 사용됩니다.

이 패턴들은 관리형 Expo 워크플로우(Expo Router, EAS, expo-* 모듈)와 New Architecture(최근 Expo SDK의 기본값이며, SDK 55+부터 필수)를 가정합니다. 브라우저 DOM을 가정하지 않습니다 — React Native에는 <div>, URL 표시줄, 웹 데이터 페칭 기본값이 없습니다.

활성화 시점

다음과 같은 경우 이 스킬을 사용하십시오:

  • React Native / Expo 화면, 컴포넌트 또는 네비게이션 구축 또는 편집
  • Expo Router로 라우팅 설정(파일 기반 app/ 디렉토리)
  • 상태가 속할 위치 결정(서버 캐시 vs 클라이언트 스토어 vs 라우트 파라미터 vs 폼)
  • TanStack Query로 데이터 페칭 연결하고 Zod로 응답 검증
  • 길거나 무거운 리스트 렌더링
  • 스타일링 접근 방식 선택 또는 적용(NativeWind 또는 StyleSheet)
  • 네이티브 디바이스 API(카메라, 위치, 알림) 또는 보안 저장소 접근
  • 모바일 특화 이슈에 대한 RN 코드 검토

웹/React-DOM 패턴(URL-as-state, <div>, SWR-for-browser)은 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

useEffect 내 fetch 대신 서버-캐시 라이브러리(TanStack Query, SWR)를 사용하십시오. 경계에서 검증하고 스키마에서 타입을 추론하십시오. 로딩, 오류 및 빈 상태를 명시적으로 처리하십시오. (예시는 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에서 큰 배열을 map 하지 마십시오

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: 훅으로 래핑, 이펙트 정리

Expo SDK 호출 및 구독은 JSX가 아닌 use* 훅 내부에 유지하십시오. 항상 정리하십시오.

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 내부에서 큰 배열을 map (가상화 없음, 끊김 현상, 높은 메모리)
<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

// 잘못됨: 번들에 실제 비밀 정보 포함
const STRIPE_SECRET = 'sk_live_...'
// 올바름: 권한이 필요한 호출은 서버 측에 유지하고, 백엔드 규칙으로 보호된 공개 키만 전송

모범 사례

  • 라우트 파일은 얇게 유지하고, 로직은 화면 컴포넌트와 use* 훅에 넣으십시오.
  • 모든 외부 입력(API 응답, 라우트 파라미터, 푸시 페이로드)을 Zod로 검증하십시오.
  • TanStack Query가 서버 상태를 소유하게 하고, 클라이언트 스토어는 작게 유지하십시오.
  • 항상 로딩, 오류 및 빈 상태를 렌더링하십시오. 대체 없이 스피너만 표시하지 마십시오.
  • 리스트를 가상화하고, renderItem을 메모이즈하며, 안정적인 keyExtractor를 제공하십시오.
  • 애니메이션에는 react-native-reanimated를 사용하십시오 (UI 스레드). JS 스레드에서 무거운 작업을 피하십시오.
  • 토큰은 expo-secure-store에 저장하고, 인증을 위해 클라이언트를 절대 신뢰하지 마십시오.
  • 처음부터 Safe Area, Dynamic Type 및 접근성 역할/레이블을 준수하십시오.
  • 릴리스 전에 모든 네이티브 종속성에 대해 New Architecture 호환성을 확인하십시오.

관련 스킬

  • frontend-patterns — React/Next.js (웹) 패턴; 공유 React 개념에 유용하지만, DOM 특화.
  • coding-standards — RN 코드에 적용되는 TypeScript/JavaScript 관용구.
  • tdd-workflow, e2e-testing — 테스트 프로세스 (RN의 경우 Jest + React Native Testing Library, Maestro/Detox 사용).
  • security-review — 위의 RN 번들/비밀 가이드라인을 보완하는 일반 보안 체크리스트.

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/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

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.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

관련 스킬