๐ฑ RN + Expo ์ฑ ๊ตฌ์ถ ์ด์ฒด์ ๊ณ ๋ ค์ฌํญ
0) ์์ ์์ฌ๊ฒฐ์ ํธ๋ฆฌ
| ์ฃผ์ | ๊ฒฐ์ ํฌ์ธํธ | ๊ถ์ฅ์ | ๋ฆฌ์คํฌ/๋์ |
|---|---|---|---|
| ํ๋ก์ ํธ ํ์ | Expo Managed vs Dev Client vs Bare | Managed๋ก ์์ โ ๋ค์ดํฐ๋ธ ์๊ตฌ ์๊ธฐ๋ฉด Dev Client | TLS Pinning/๊ณ ๊ธ BLE/๋ฐฑ๊ทธ๋ผ์ด๋ ์ ๋ก๋ ๊ฐํ๋ฉด Bare ๊ณ ๋ ค |
| ๋ผ์ฐํ | React Navigation vs Expo Router | React Navigation (์์ /ํ์ฅ) | ํ์ผ ๊ธฐ๋ฐ ์ํ๋ฉด Expo Router(+Navigation) |
| ๋์์ธ ์์คํ | ํ ํฐ/Variant/์ ๊ทผ์ฑ ์ ๋ต | Restyle + RN Aria (gluestack ์ผ๋ถ ๋ณํ ๊ฐ๋ฅ) | ํํท(Paper/Elements) ์์กด ์ปค์ง๋ฉด ์ปค์คํ ๋์ด๋โ |
| ์ํ์ ๋ต | ๋ก์ปฌ/์๋ฒ ์ํ ๋ถ๋ฆฌ | Zustand(Local) + React Query(Server) | ์ ์ญ ๋จ์ฉ ์ง์, ํ๋ฉด ๋จ์ co-locate |
| ๋ฐฐํฌ์ ๋ต | OTA/์คํ ์ด ๋ฆด๋ฆฌ์ค | EAS Update + EAS Build/Submit | ๋ค์ดํฐ๋ธ ๋ณ๊ฒฝ์ ์ ๋น๋ ํ์ |
| ํ๊ฒฝ๋ถ๋ฆฌ | dev/stage/prod | app.config.ts + EAS Secrets | .env ์ง์ ์ฃผ์ /์ ์ถ ๊ธ์ง |
1) ๊ตฌ์กฐ/์ํคํ ์ฒ
- ํด๋ ๊ตฌ์กฐ(์: FSD or feature-first)
app/(screens, navigation),entities/,features/,shared/(ui, lib, api),processes/(์ธ์ ๋ฑ)shared/ui:Box/Text/Button๋ฒ ์ด์ค ์ปดํฌ๋ํธ(ํ ํฐ/Variant ๋ฐ์)
- ๊ฒฝ๊ณ ๋ถ๋ฆฌ
- Server state(React Query) vs UI/Session state(Zustand) ๋ช ํ ๋ถ๋ฆฌ
- API ๋ชจ๋: fetcher(axios) + ์ธํฐ์ ํฐ(ํ ํฐ/๋ฆฌํ๋ ์)
- ์์กด์ฑ ๊ท์น
- ์์ โ ํ์ ๋ฐฉํฅ(์ํ ๊ธ์ง), public API๋ง import
2) ์ ๊ทผ์ฑ(A11y) ์ ์ฑ
- ํฐ์น ํ๊น 44dp ์ด์, ์ฝํธ๋ผ์คํธ 4.5:1 ์ค์
- ๋ชจ๋ ํฐ์น ์์์ accessibilityRole/Label/Hint ์ง์
- ํฌ์ปค์ค ๊ด๋ฆฌ: ๋ชจ๋ฌ/๋ฐํ ์ํธ ์คํ ์ ์ด์ ์ด๋/ํธ๋ฉ
- ์คํฌ๋ฆฐ๋ฆฌ๋(VoiceOver/TalkBack) ์๋ ํ ์คํธ ํ๋ก์ฐ ๋ฌธ์ํ
- ์ ์ค์ฒ๋ง์ผ๋ก ์๋ํ์ง ์๋๋ก ๋์ฒด ์กฐ์ ์ ๊ณต
- ํต์ฌ ์ปดํฌ๋ํธ์ RN Aria ํ ์ฐ์ ์ ์ฉ (button, checkbox, slider ๋ฑ)
3) ์ฑ๋ฅ/UX ๊ฐ์ด๋
- ๋ฆฌ์คํธ๋ ๊ธฐ๋ณธ
FlatListโ ์ค์ผ์ผ๋๋ฉด FlashList๋ก ์ ํ - ์ด๋ฏธ์ง: WebP/AVIF ์ฐ์ , ์บ์(expo-file-system) + ํ๋ฆฌ๋ก๋ ์ ๋ต
- ์ ๋๋ฉ์ด์ : Reanimated + Moti, FPS ๋๋ ๊ตฌ๊ฐ(์คํฌ๋กค/์ํธ) ์ฐ์ ์ต์ ํ
- ํผํฌ๋จผ์ค ์์ฐ ์ ์(์ฒซ ๋ ๋ < 1.5s, ๋ฉ๋ชจ๋ฆฌ, JS bundle ํฌ๊ธฐ)
- ์ธํฐ๋์ ๋ ์ดํด์: ์ ์ค์ฒ-์ค์ฌ ํ๋ฉด(๋ฐํ ์ํธ/์บ๋ฌ์ )์์ 16ms ํ๊น
4) ๋คํธ์ํน/์คํ๋ผ์ธ
- Axios ์ธํฐ์ ํฐ: ํ ํฐ ์ฃผ์ /401 ๋ฆฌํ๋ ์/์ฌ์๋(์ง์๋ฐฑ์คํ)
- React Query:
- ์บ์ ํ์/์คํ ์ผ ํ์ ์ ์ฑ , ์๋ฌ ๊ฒฝ๊ณ ์ฒ๋ฆฌ(ํ ์คํธ/๋ค์ด์ผ๋ก๊ทธ)
- Persist:
react-query-persist-client + AsyncStorage(์คํ๋ผ์ธ ์ฝ๊ธฐ)
- ์คํ๋ผ์ธ ์ ๋ต: ์ฝ๊ธฐ ์บ์ + ์ฐ๊ธฐ ํ(๋คํธ์ํฌ ๋ณต๊ตฌ ์ ๋๊ธฐํ)
- ๋ฐ์ดํฐ ๋ณดํธ: ๋ฏผ๊ฐ์ ๋ณด๋ SecureStore, ์ผ๋ฐ์ AsyncStorage
5) ์๋ฆผ/๋งํฌ/๋ฅ๋งํฌ
- ํธ์ ์๋ฆผ:
expo-notifications- Android ์ฑ๋ ์ฌ์ ์์ฑ(์ค์๋, ์ฌ์ด๋), ์นดํ ๊ณ ๋ฆฌ(์ก์ ๋ฒํผ) ์ค๊ณ
- ํ ํฐ ์์ง/ํด์ง/ํ ํฝ ๊ตฌ๋ ๋ฐฑ์๋ ์คํ ๋ช ์ธ
- ๋ก์ปฌ ์๋ฆผ: ์ค์ผ์ค/๋ฐ๋ณต, ๊ถํ ๊ฑฐ๋ถ UX ๋๋น
- ๋ฅ๋งํฌ:
expo-linking์ผ๋ก ์คํด/์ ๋๋ฒ์ค ๋งํฌ ์ค์ (๊ฒ์ฆ ํ์ผ ํฌํจ)- ์ธ๋ถ ์ฑ ๋ฏธ์ค์น/์คํจ ์ Web Fallback ์ค๋น
- ์ธ์ฑ ๋ธ๋ผ์ฐ์ :
expo-web-browser(์ธ์ ์ฝ๋ฐฑ/๋ฆฌ๋๋ ์ ์ฒ๋ฆฌ)
6) ๋ณด์/๊ฐ์ธ์ ๋ณด
- ํ ํฐ/์ธ์ : SecureStore ์ ์ฅ, ๋ง๋ฃ/๊ฐฑ์ ๋ก์ง ํ์คํ
- ์ ์ก ์ํธํ: HSTS/์ต์ TLS, (๋ง์ฝ ํ์ํ ๊ฒฝ์ฐ) Pinning์ Bare/Dev Client์์ ๊ฒํ
- ๋ก๊ทธ/๋ถ์์ PII ๊ธ์ง. ๋ง์คํน/ํด์ ๊ท์น ๋ฌธ์ํ
- ๊ถํ ํ๋กฌํํธ ๋ฌธ๊ตฌ๋ ์ ํํ ์ฉ๋์ ๊ฐ์น๋ฅผ ์ค๋ช
- ์คํฌ๋ฆฐ์ท ์ฐจ๋จ(๋ฏผ๊ฐํ๋ฉด) ํ์ ์ Android ํ๋๊ทธ/Bare ๊ฒํ
7) ๋ถ์/๋ชจ๋ํฐ๋ง/๋ก๊ทธ
- ํฌ๋์/์ฑ๋ฅ:
@sentry/react-native(์์ค๋งต ์ ๋ก๋ ์๋ํ) - ์ด๋ฒคํธ ๋ถ์: Segment/Amplitude/Firebase Analytics ์ค ํ๋
- ์ด๋ฒคํธ ๋ช ์ธ์(์ด๋ฆ/ํ๋ผ๋ฏธํฐ/์ํ๋ง/PII ๊ท์น) ์์ฑ
- ์ฌ์ฉ์ ํผ๋๋ฐฑ: ์ธ์ฑ ํผ๋๋ฐฑ/๋ก๊ทธ ์์ง(๋์/์ตํธ์์ ์ ๊ณต)
- ์๊ฒฉ ํ๋๊ทธ: ๊ธฐ๋ฅ ํ ๊ธ(LaunchDarkly/ConfigCat/์์ฒด)๋ก ์์ ํ ๋กค์์
8) ๊ตญ์ ํ(i18n)/ํ์งํ
react-i18next+expo-localization- ๋ฌธ์์ด ํค์ปจ๋ฒค์ /ํ๋ ์ด์คํ๋ ๊ท์ฝ, ๋ฒ์ญ ์ํฌํ๋ก์ฐ(Figma/Sheet โ JSON)
- ๋ ์ง/ํตํ ํฌ๋งท, ์ฐ/์ข ์ ๋ ฌ, ํ๋ฌ๋ด ๊ท์น ๋ฐ์
9) ๋น๋/๋ฐฐํฌ/๋ฆด๋ฆฌ์ค
- EAS Build/Submit ํ์ดํ๋ผ์ธ(GitHub Actions)
- ์คํ ์ด ์๋ช /ํ๋ก๋น์ ๋ ์๋ ๊ด๋ฆฌ, ๋น๋ ๋ฒํธ/๋ฒ์ ์ฆ๊ฐ ์๋ํ
- EAS Update(OTA): ๋ง์ด๋ ์์ ์ OTA, ๋ค์ดํฐ๋ธ ๋ฐ๋๋ฉด ์ ๋น๋
- ๋ฆด๋ฆฌ์ค ํธ๋: ๋ด๋ถ โ ํ ์คํธ โ ํ๋ก๋์ ๋จ๊ณ์ ๋กค์์
- ๋ฆด๋ฆฌ์ค ๋ ธํธ ํ ํ๋ฆฟ/์ฒดํฌ๋ฆฌ์คํธ(Screenshots, ํ๋ผ์ด๋ฒ์, ์ฌ์ฌ ๋์ Q&A)
10) ํ์ง/ํ ์คํธ ์ ๋ต
- ์ ๋/์ปดํฌ๋ํธ: Jest + Testing Library(RN)
- ํตํฉ/E2E: Maestro(Expo ์นํ), ํ์ ์ Detox(Dev/Bare)
- ์ ๊ทผ์ฑ ํ ์คํธ: ์คํฌ๋ฆฐ๋ฆฌ๋ ์๋๋ฆฌ์ค ๋ฌธ์ + ์๋ ํ ์คํธ
- ๋๋ฐ์ด์ค ๋งคํธ๋ฆญ์ค: iOS(์ต์/์ต์ ) ร Android(์ค์ ๊ฐ ํฌํจ) ์ค์ ๊ธฐ๊ธฐ
- ์ฑ๋ฅ ํ๊ท: ์คํฌ๋กค/๋ฐํ ์ํธ/๋ฆฌ์คํธ FPS ์ธก์ ์ฒดํฌ
11) ์ค์ /๊ถํ/์ฑ ๊ตฌ์ฑ
app.config.ts์์ env๋ณ ๋ฒ๋คID/์คํด/์์ด์ฝ/์คํ๋์ ๋ถ๋ฆฌ- iOS
infoPlist/Androidpermissions๊ถํ ์ ์ธ + ์ฌ์ ๋ฌธ๊ตฌ - ๋ฅ๋งํฌ ์คํด ๋ช ๋ช ๊ท์น(์ค๋ณต ๋ฐฉ์ง), ์ ๋๋ฒ์ค ๋งํฌ ๋๋ฉ์ธ ์์ ๊ฒ์ฆ
12) ์คํ ์ด ์ปดํ๋ผ์ด์ธ์ค/์ ์ฑ
- ๊ฐ์ธ์ ๋ณด ์ฒ๋ฆฌ๋ฐฉ์นจ/์ด์ฉ์ฝ๊ด ์ฑ ๋ด ๋งํฌ ์ ๊ณต
- Sign in with Apple(iOS, ํ ์์ ๋ก๊ทธ์ธ ์์ ๋)
- ์์น/๋ฐฑ๊ทธ๋ผ์ด๋/๋ น์ ๋ฑ ๋ฏผ๊ฐ๊ถํ ํ์์ฑ ๋ช ํํ(๊ธฐ๋ฅ ์์ผ๋ฉด ์ ๊ฑฐ)
- ํธ์ ์ด์ฉ ๋ชฉ์ ์ค๋ช /์ตํธ์ธ/์ตํธ์์ UX ์ ๊ณต
13) ํ ์ด์/๋ ํฌ/CI
- ๋ชจ๋
ธ๋ ํฌ(Turborepo + pnpm) ๊ณ ๋ ค: web/rn ๊ณตํต ํจํค์ง(
ui,config,api) - ์ฝ๋ ๊ท์น: TypeScript strict, Biome(ํฌ๋งท/๋ฆฐํธ), commitlint + conventional commits
- PR ํ ํ๋ฆฟ(ํ ์คํธ ๋ฒ์/์คํฌ๋ฆฐ์ท/์ ๊ทผ์ฑ ์ฒดํฌ ํฌํจ)
- ๋ณ๊ฒฝ ๋ก๊ทธ ์๋ํ(Changesets)
14) ๋ฐ์ดํฐ ๋๊ธฐํ/์คํ๋ผ์ธ ์ฌํ(์ ํ)
- ์ถฉ๋ ํด๊ฒฐ ์ ์ฑ : Last-Write-Wins vs Merge vs ์๋ฒ ๊ถํ
- ํ ์ ์ฅ: ์คํ๋ผ์ธ ๋ณ๊ฒฝ๋ถ ๋ก์ปฌ ํ โ ์จ๋ผ์ธ ์ ์ฌ์
- ๋ถ๋ถ ๋๊ธฐํ: ๋ทฐ ๋จ์ prefetch(React Query) + TTL
15) ์ํ์์ธ(Risk Register) ์์
| ๋ฆฌ์คํฌ | ์ํฅ | ๋์ |
|---|---|---|
| ๊ถํ ๊ฑฐ๋ถ๋ก ํต์ฌ ๊ธฐ๋ฅ ๋ถ๊ฐ | ๊ธฐ๋ฅ ๋ฏธ์ฌ์ฉ/์ดํ | ์ต์ด ์ง์ ์จ๋ณด๋ฉ์์ ๊ฐ์น ์ค๋ช โ ์ค์ ํ๋ฉด ์ด๋ ๋ฒํผ ์ ๊ณต |
| ์๋๋ก์ด๋ ๊ธฐ๊ธฐ ํธ์ฐจ | ํฌ๋์/UX ๋ถ์ผ์น | ์ค์ ๊ฐ ์ค์ ๊ธฐ๊ธฐ ํ ์คํธ, ์ฑ๋/ํ์ผ/์คํ ๋ฆฌ์ง ์ ์ฑ ์ฌ์ ๊ฒ์ฆ |
| ์๋ฆผ ๋ฏธ๋์ฐฉ/ํ ํฐ ๋ง๋ฃ | ํต์ฌ ์ด๋ฒคํธ ๋๋ฝ | ํ ํฐ ์ฌ๋ฐ๊ธ ๋ก์ง/์๋ฒ ๋ณด์ , ์๋ฆผ ์ํ self-check ํ๋ฉด |
| ๋ฒ๋ค ํฌ๊ธฐ ์ฆ๊ฐ | ์ค์น/์ ๋ฐ์ดํธ ์ดํ | ์์ด์ฝ/์ด๋ฏธ์ง ์ต์ ํ, ์ฃฝ์ ์์กด์ฑ ์ ๊ฑฐ, ์ฝ๋ ์คํ๋ฆฟ ๊ณ ๋ ค |
| API ๋ณ๊ฒฝ/์๋ ์ด์ | ์ฅ์ /ํ์์์ | ๋ฒ์ ๋ API ๊ณ์ฝ, ๋ฆฌํธ๋ผ์ด/๋ฐฑ์คํ/์ํท๋ธ๋ ์ด์ปค |
๋น ๋ฅธ ์คํ ์ฒดํฌ๋ฆฌ์คํธ(DoD)
- ๋์์ธ ํ ํฐ/Variant ์ด์ ํ์ (์/๊ฐ๊ฒฉ/ํฐํธ/๋ผ์ด๋/์ํ)
- RN Aria + Restyle ๊ธฐ๋ฐ Button/Input/Card/Modal/Sheet ๋ฒ ์ด์ค ๊ตฌ์ถ
- React Navigation + ๋ฅ๋งํฌ/์ด๊ธฐ ๋ฃจํธ ์ธํ
- ๊ถํ ํ๋กฌํํธ ๋ฌธ๊ตฌ/ํ๋ฆ ํ์ (์นด๋ฉ๋ผ/์จ๋ฒ/์์น/์๋ฆผ)
- ์๋ฆผ ์ฑ๋(Android) ๋ฐ Expo Push ํ ํฐ ๋ฐ๊ธ/์๋ฒ ์ฐ๋
- React Query ์บ์/์๋ฌ ๊ฒฝ๊ณ/๋ก๊น (Sentry) ์ ์ฉ
- EAS Build/Submit ํ์ดํ๋ผ์ธ + OTA(Update) ๊ตฌ์ฑ
- i18n ์ธํ + ๊ธฐ๋ณธ ๋ฒ์ญ ํ์ผ
- ํ ์คํธ: Jest/RTL + Maestro ์ค๋ชจํฌ ํ๋ก์ฐ
- ์คํ ์ด ์ ์ถ ์ฒดํฌ๋ฆฌ์คํธ/๊ฐ์ธ์ ๋ณด ๋ฌธ์ ์ค๋น