- harness·zio-harness·proposal-builder·zioinfo → plugins/zioinfo (git mv 히스토리 보존) - 스킬 4·커맨드 3(/zioinfo:pmo·proposal·wiki)·에이전트 15·graphify 훅·knowledge 통합 - 신규: /zioinfo:wiki (graphify LLM wiki — graphify-out/wiki/ 커뮤니티별 아티클) - 신규: ZIO WISE 테마 (themes/zioinfo.json, experimental) - manifest 최신화: $schema·displayName(ZIO INFOTECH Suite)·experimental.themes - marketplace.json 단일 엔트리, 루트 plugin.json 제거 - CLAUDE.md·PROJECT_MAP·docs/plugins.md·README 3종·CHANGELOG·설치가이드 pptx 재구성 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
206 lines
5.0 KiB
Markdown
206 lines
5.0 KiB
Markdown
# 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 (
|
|
<NavigationContainer>
|
|
{token ? <MainNavigator /> : <AuthNavigator />}
|
|
</NavigationContainer>
|
|
);
|
|
}
|
|
|
|
// src/navigation/MainNavigator.tsx
|
|
const Tab = createBottomTabNavigator<MainTabParams>();
|
|
|
|
export function MainNavigator() {
|
|
return (
|
|
<Tab.Navigator>
|
|
<Tab.Screen name="Home" component={HomeScreen} />
|
|
<Tab.Screen name="Profile" component={ProfileScreen} />
|
|
</Tab.Navigator>
|
|
);
|
|
}
|
|
|
|
// 타입 안전한 네비게이션
|
|
type MainTabParams = {
|
|
Home: undefined;
|
|
Profile: { userId: string };
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
## 화면 컴포넌트 패턴
|
|
|
|
```tsx
|
|
// src/screens/home/HomeScreen.tsx
|
|
import { NativeStackScreenProps } from '@react-navigation/native-stack';
|
|
|
|
type Props = NativeStackScreenProps<MainTabParams, 'Home'>;
|
|
|
|
export function HomeScreen({ navigation }: Props) {
|
|
const { data: posts, isLoading } = usePosts();
|
|
|
|
if (isLoading) return <LoadingSpinner />;
|
|
|
|
return (
|
|
<SafeAreaView style={styles.container}>
|
|
<FlatList
|
|
data={posts}
|
|
keyExtractor={(item) => item.id.toString()}
|
|
renderItem={({ item }) => (
|
|
<PostCard
|
|
post={item}
|
|
onPress={() => navigation.navigate('PostDetail', { id: item.id })}
|
|
/>
|
|
)}
|
|
/>
|
|
</SafeAreaView>
|
|
);
|
|
}
|
|
|
|
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<AuthState>(
|
|
(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` 확장자 |
|