# Mobile App 개발 가이드 ## 스택 가정 - **React Native** (Expo 또는 bare workflow) + TypeScript - 네비게이션: React Navigation v6 - 상태 관리: Zustand (웹과 공유 가능) - API: Axios + TanStack Query - 스타일: StyleSheet (네이티브), NativeWind (Tailwind 기반) > Flutter를 사용하는 경우: `references/flutter.md` 생성 필요 (별도 확장) --- ## 폴더 구조 ``` mobile/ ├── src/ │ ├── screens/ # 화면 단위 컴포넌트 │ │ ├── auth/ # 인증 화면 (Login, Register) │ │ ├── home/ # 홈 화면 │ │ └── profile/ # 프로필 화면 │ ├── navigation/ # 네비게이터 정의 │ │ ├── RootNavigator.tsx │ │ ├── AuthNavigator.tsx │ │ └── MainNavigator.tsx │ ├── components/ # 재사용 컴포넌트 │ │ ├── ui/ # 기본 UI (Button, Input, Card) │ │ └── features/ # 도메인별 컴포넌트 │ ├── api/ # API 클라이언트 + 훅 (웹과 동일 패턴) │ ├── store/ # Zustand stores (웹과 코드 공유 가능) │ ├── types/ # 타입 정의 │ ├── utils/ # 유틸리티 │ └── constants/ # 상수 (colors, sizes, etc.) ├── android/ # Android 네이티브 ├── ios/ # iOS 네이티브 ├── app.json # Expo 설정 └── package.json ``` --- ## 네비게이션 구조 ```tsx // src/navigation/RootNavigator.tsx export function RootNavigator() { const { token } = useAuthStore(); return ( {token ? : } ); } // src/navigation/MainNavigator.tsx const Tab = createBottomTabNavigator(); export function MainNavigator() { return ( ); } // 타입 안전한 네비게이션 type MainTabParams = { Home: undefined; Profile: { userId: string }; }; ``` --- ## 화면 컴포넌트 패턴 ```tsx // src/screens/home/HomeScreen.tsx import { NativeStackScreenProps } from '@react-navigation/native-stack'; type Props = NativeStackScreenProps; export function HomeScreen({ navigation }: Props) { const { data: posts, isLoading } = usePosts(); if (isLoading) return ; return ( item.id.toString()} renderItem={({ item }) => ( navigation.navigate('PostDetail', { id: item.id })} /> )} /> ); } const styles = StyleSheet.create({ container: { flex: 1, backgroundColor: '#fff' }, }); ``` --- ## API 연동 (웹과 코드 공유) 웹(React)과 모바일은 동일한 API 훅 패턴을 사용한다. 공유 패키지 또는 복사 방식으로 관리: ```typescript // src/api/client.ts — 웹과 동일한 패턴 const api = axios.create({ baseURL: Config.API_URL, // react-native-config 사용 timeout: 10000, }); ``` 환경 변수는 `react-native-config` 사용: ``` # .env API_URL=http://localhost:8080 ``` --- ## 로컬 저장소 ```typescript import AsyncStorage from '@react-native-async-storage/async-storage'; // 토큰 저장 await AsyncStorage.setItem('token', token); // 토큰 읽기 const token = await AsyncStorage.getItem('token'); // Zustand 영속성 (zustand/middleware의 persist) const useAuthStore = create( persist( (set) => ({ ... }), { name: 'auth-store', storage: createJSONStorage(() => AsyncStorage) } ) ); ``` --- ## 푸시 알림 ```typescript // Expo Notifications 사용 시 import * as Notifications from 'expo-notifications'; async function registerForPushNotifications() { const { status } = await Notifications.requestPermissionsAsync(); if (status !== 'granted') return null; const token = await Notifications.getExpoPushTokenAsync({ projectId: Constants.expoConfig?.extra?.eas?.projectId, }); // 서버에 토큰 등록 await api.post('/api/v1/users/push-token', { token: token.data }); return token.data; } ``` --- ## 빌드 및 배포 ```bash # 개발 실행 npx expo start # Android 빌드 (EAS Build) eas build --platform android --profile preview # iOS 빌드 eas build --platform ios --profile preview # 스토어 제출 eas submit --platform android eas submit --platform ios ``` --- ## 컨벤션 | 항목 | 규칙 | |------|------| | 화면 파일 | `{Name}Screen.tsx` | | 네비게이터 | `{Name}Navigator.tsx` | | 스타일 | 파일 하단에 `StyleSheet.create({})` | | 네이티브 모듈 | `src/native/` 폴더에 분리 | | 플랫폼별 코드 | `.ios.tsx` / `.android.tsx` 확장자 |