Expo Router๋ก ๋ฐฐ์ฐ๋ React Native ํ์ผ ๊ธฐ๋ฐ ๋ด๋น๊ฒ์ด์ ์๋ฒฝ ๊ฐ์ด๋
Expo Router๋ฅผ ํ์ฉํ React Native ํ์ผ ๊ธฐ๋ฐ ๋ผ์ฐํ ์ ์ฒด๊ณ์ ์ผ๋ก ๋ค๋ฃน๋๋ค. ๋ ์ด์์, ๋์ ๋ผ์ฐํธ, ํ์ ์์ ๋ด๋น๊ฒ์ด์ , ํญ, ๋ชจ๋ฌ, ๋ฏธ๋ค์จ์ด ๋ฑ 2026๋ ์ต์ ํจํด์ ์ข ํฉ์ ์ผ๋ก ์ค๋ช ํฉ๋๋ค.

Expo Router๋ React Native ์ ํ๋ฆฌ์ผ์ด์ ์์ ํ์ผ ์์คํ ๊ธฐ๋ฐ์ ๋ผ์ฐํ ์ ๊ตฌํํ ์ ์๊ฒ ํด์ฃผ๋ ๊ฐ๋ ฅํ ๋ด๋น๊ฒ์ด์ ํ๋ ์์ํฌ์ ๋๋ค. Next.js์์ ์๊ฐ์ ๋ฐ์ ์ด ๋ผ์ฐํฐ๋ ํ์ผ๊ณผ ํด๋ ๊ตฌ์กฐ๋ง์ผ๋ก ์ฑ์ ์ ์ฒด ๋ด๋น๊ฒ์ด์ ์ ์๋์ผ๋ก ์์ฑํฉ๋๋ค. ๊ธฐ์กด React Navigation์์ ์๋์ผ๋ก ์คํฌ๋ฆฐ์ ๋ฑ๋กํ๊ณ ๋ค๋น๊ฒ์ดํฐ๋ฅผ ์ค์ฒฉํ๋ ๋ณต์กํ ์ค์ ๊ณผ์ ์ ํ๊ธฐ์ ์ผ๋ก ๋จ์ํํ๋ฉฐ, ์น๊ณผ ๋ชจ๋ฐ์ผ ํ๋ซํผ ๋ชจ๋์์ ์ผ๊ด๋ ๋ผ์ฐํ ๊ฒฝํ์ ์ ๊ณตํฉ๋๋ค. ์ด ๊ฐ์ด๋์์๋ Expo Router์ ํต์ฌ ๊ฐ๋ ๋ถํฐ ๋์ ๋ผ์ฐํธ, ํ์ ์์ ๋ผ์ฐํ , ์ธ์ฆ ๊ฐ๋๊น์ง ์ค๋ฌด์์ ํ์ํ ๋ชจ๋ ๋ด๋น๊ฒ์ด์ ํจํด์ ์ฒด๊ณ์ ์ผ๋ก ๋ค๋ฃน๋๋ค.
Expo Router ํ๋ก์ ํธ๋ฅผ ์์ํ๋ ๊ฐ์ฅ ๋น ๋ฅธ ๋ฐฉ๋ฒ์ npx create-expo-app@latest --template tabs ๋ช
๋ น์ด๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์
๋๋ค. ์ด ํ
ํ๋ฆฟ์๋ ํ์ผ ๊ธฐ๋ฐ ๋ผ์ฐํ
, ํญ ๋ด๋น๊ฒ์ด์
, TypeScript ์ค์ ์ด ๋ชจ๋ ํฌํจ๋์ด ์์ด ๋ณ๋์ ๊ตฌ์ฑ ์์ด ๋ฐ๋ก ๊ฐ๋ฐ์ ์์ํ ์ ์์ต๋๋ค.
Expo Router์ ํ์ผ ๊ธฐ๋ฐ ๋ผ์ฐํ ์๋ฆฌ
Expo Router์ ํต์ฌ ์ฒ ํ์ ๋จ์ํฉ๋๋ค. app/ ๋๋ ํ ๋ฆฌ ์์ ํ์ผ์ ์์ฑํ๋ฉด, ํด๋น ํ์ผ์ ๊ฒฝ๋ก๊ฐ ๊ณง ์ฑ์ URL์ด ๋ฉ๋๋ค. app/settings.tsx ํ์ผ์ ๋ง๋ค๋ฉด /settings ๊ฒฝ๋ก๊ฐ ์๋์ผ๋ก ๋ฑ๋ก๋๊ณ , app/profile/edit.tsx ํ์ผ์ ๋ง๋ค๋ฉด /profile/edit ๊ฒฝ๋ก๊ฐ ์์ฑ๋ฉ๋๋ค. ๋ณ๋์ ๋ผ์ฐํ
์ค์ ํ์ผ์ด๋ ๋ค๋น๊ฒ์ดํฐ ๋ฑ๋ก ์ฝ๋๊ฐ ํ์ํ์ง ์์ต๋๋ค.
์ด๋ฌํ ํ์ผ ๊ธฐ๋ฐ ์ ๊ทผ ๋ฐฉ์์ ์ฌ๋ฌ ๊ฐ์ง ์ค์ง์ ์ธ ์ด์ ์ ์ ๊ณตํฉ๋๋ค. ์ฒซ์งธ, ํ๋ก์ ํธ์ ํ์ผ ๊ตฌ์กฐ๋ฅผ ๋ณด๋ ๊ฒ๋ง์ผ๋ก ์ฑ์ ์ ์ฒด ๋ด๋น๊ฒ์ด์ ํ๋ฆ์ ํ์ ํ ์ ์์ต๋๋ค. ๋์งธ, ์๋ก์ด ํ๋ฉด์ ์ถ๊ฐํ ๋ ํ์ผ ํ๋๋ง ์์ฑํ๋ฉด ๋๋ฏ๋ก ๊ฐ๋ฐ ์๋๊ฐ ํฌ๊ฒ ํฅ์๋ฉ๋๋ค. ์ ์งธ, ๋ฅ ๋งํน์ด ๊ธฐ๋ณธ์ ์ผ๋ก ์ง์๋์ด ์ธ๋ถ์์ ์ฑ์ ํน์ ํ๋ฉด์ผ๋ก ์ง์ ์ด๋ํ๋ ๊ฒ์ด ์์ฐ์ค๋ฝ๊ฒ ๊ฐ๋ฅํฉ๋๋ค.
๋ค์์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ํ ํ๋ฉด ๊ตฌํ ์์์
๋๋ค. Link ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ์ฌ ๋ค๋ฅธ ํ๋ฉด์ผ๋ก์ ๋ด๋น๊ฒ์ด์
์ ์ ์ธ์ ์ผ๋ก ์ฒ๋ฆฌํ ์ ์์ต๋๋ค.
import { View, Text, StyleSheet } from 'react-native'
import { Link } from 'expo-router'
export default function HomeScreen() {
return (
<View style={styles.container}>
<Text style={styles.title}>Welcome</Text>
{/* Link maps directly to file path */}
<Link href="/settings" style={styles.link}>
Open Settings
</Link>
<Link href="/profile/edit" style={styles.link}>
Edit Profile
</Link>
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, justifyContent: 'center', padding: 24 },
title: { fontSize: 28, fontWeight: 'bold', marginBottom: 16 },
link: { fontSize: 16, color: '#61DAFB', marginTop: 12 },
})Link ์ปดํฌ๋ํธ์ href ์์ฑ์ ์ ๋ฌํ๋ ๊ฒฝ๋ก๋ app/ ๋๋ ํ ๋ฆฌ ๋ด์ ์ค์ ํ์ผ ๊ฒฝ๋ก์ ์ ํํ ์ผ์นํฉ๋๋ค. ์ด ์ง๊ด์ ์ธ ๋งคํ ๋๋ถ์ ๋ด๋น๊ฒ์ด์
์ฝ๋์ ์๋๋ฅผ ์ฆ์ ํ์
ํ ์ ์์ผ๋ฉฐ, ์๋ชป๋ ๊ฒฝ๋ก๋ฅผ ์ฐธ์กฐํ ๊ฐ๋ฅ์ฑ๋ ํฌ๊ฒ ์ค์ด๋ญ๋๋ค.
ํ๋ก์ ํธ ๊ตฌ์กฐ์ ๋ ์ด์์ ํ์ผ
Expo Router ํ๋ก์ ํธ์์ ํ์ผ๊ณผ ํด๋์ ๊ตฌ์กฐ๋ ๊ณง ์ฑ์ ๋ด๋น๊ฒ์ด์ ์ํคํ ์ฒ๋ฅผ ์๋ฏธํฉ๋๋ค. ๊ฐ ํ์ผ๊ณผ ํน์ ๋ช ๋ช ๊ท์น์ด ์ด๋ค ์ญํ ์ ํ๋์ง ์ดํดํ๋ ๊ฒ์ด ํจ๊ณผ์ ์ธ ๋ผ์ฐํ ์ค๊ณ์ ์ฒซ๊ฑธ์์ ๋๋ค.
app/
_layout.tsx # Root layout (Stack or custom)
index.tsx # Home screen (/)
(tabs)/ # Tab group (parentheses = route group)
_layout.tsx # Tab navigator
home.tsx # /home tab
search.tsx # /search tab
profile.tsx # /profile tab
settings/
_layout.tsx # Settings stack layout
index.tsx # /settings
notifications.tsx # /settings/notifications
privacy.tsx # /settings/privacy์ด ๊ตฌ์กฐ์์ ์ฃผ๋ชฉํด์ผ ํ ๋ช ๊ฐ์ง ํต์ฌ ๊ท์น์ด ์์ต๋๋ค. _layout.tsx ํ์ผ์ ํด๋น ๋๋ ํ ๋ฆฌ์ ๊ทธ ํ์ ๊ฒฝ๋ก๋ค์ ๊ฐ์ธ๋ ๋ ์ด์์ ๋ํผ ์ญํ ์ ํฉ๋๋ค. ์ด ํ์ผ์ URL ๊ฒฝ๋ก์ ํฌํจ๋์ง ์์ผ๋ฉฐ, Stack, Tabs ๋ฑ์ ๋ค๋น๊ฒ์ดํฐ๋ฅผ ์ ์ํ๋ ๋ฐ ์ฌ์ฉ๋ฉ๋๋ค. index.tsx ํ์ผ์ ํด๋น ๋๋ ํ ๋ฆฌ์ ๊ธฐ๋ณธ ๊ฒฝ๋ก๋ฅผ ๋ํ๋
๋๋ค. ๊ดํธ๋ก ๊ฐ์ผ ํด๋๋ช
(์: (tabs))์ ๋ผ์ฐํธ ๊ทธ๋ฃน์ผ๋ก, URL์ ์ํฅ์ ์ฃผ์ง ์์ผ๋ฉด์ ๊ด๋ จ ํ๋ฉด๋ค์ ๋
ผ๋ฆฌ์ ์ผ๋ก ๋ฌถ์ ์ ์๊ฒ ํด์ค๋๋ค.
๋ฃจํธ ๋ ์ด์์ ํ์ผ์ ์ฑ์ ์ต์์ ๋ด๋น๊ฒ์ด์ ๊ตฌ์กฐ๋ฅผ ์ ์ํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก Stack ๋ค๋น๊ฒ์ดํฐ๋ฅผ ์ฌ์ฉํ์ฌ ์ ์ฒด ์ฑ์ ํ๋ฉด ์ ํ ๋ฐฉ์์ ์ค์ ํฉ๋๋ค.
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
<Stack
screenOptions={{
headerStyle: { backgroundColor: '#1a1a2e' },
headerTintColor: '#ffffff',
headerTitleStyle: { fontWeight: '600' },
}}
>
<Stack.Screen name="index" options={{ title: 'Home' }} />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
<Stack.Screen name="settings" options={{ title: 'Settings' }} />
</Stack>
)
}screenOptions ์์ฑ์ ํตํด ๋ชจ๋ ํ์ ํ๋ฉด์ ๊ณตํต์ผ๋ก ์ ์ฉ๋ ํค๋ ์คํ์ผ์ ์ง์ ํ ์ ์์ผ๋ฉฐ, ๊ฐ๋ณ Stack.Screen์ options๋ฅผ ํตํด ํน์ ํ๋ฉด์ ์ค์ ์ ์ค๋ฒ๋ผ์ด๋ํ ์ ์์ต๋๋ค. (tabs) ๋ผ์ฐํธ ๊ทธ๋ฃน์ headerShown: false๋ฅผ ์ค์ ํ ๊ฒ์ ํญ ๋ค๋น๊ฒ์ดํฐ๊ฐ ์์ฒด์ ์ผ๋ก ํค๋๋ฅผ ๊ด๋ฆฌํ๊ธฐ ๋๋ฌธ์
๋๋ค.
Expo Router๋ก ํญ ๋ด๋น๊ฒ์ด์ ๊ตฌํํ๊ธฐ
ํญ ๋ด๋น๊ฒ์ด์
์ ๋ชจ๋ฐ์ผ ์ฑ์์ ๊ฐ์ฅ ๋ณดํธ์ ์ผ๋ก ์ฌ์ฉ๋๋ ๋ด๋น๊ฒ์ด์
ํจํด์
๋๋ค. Expo Router์์๋ ๋ผ์ฐํธ ๊ทธ๋ฃน๊ณผ Tabs ์ปดํฌ๋ํธ๋ฅผ ์กฐํฉํ์ฌ ์ง๊ด์ ์ผ๋ก ํญ ๋ด๋น๊ฒ์ด์
์ ๊ตฌํํ ์ ์์ต๋๋ค.
import { Tabs } from 'expo-router'
import { Ionicons } from '@expo/vector-icons'
export default function TabLayout() {
return (
<Tabs
screenOptions={{
tabBarActiveTintColor: '#61DAFB',
tabBarInactiveTintColor: '#888',
tabBarStyle: {
backgroundColor: '#1a1a2e',
borderTopColor: '#2d2d44',
},
}}
>
<Tabs.Screen
name="home"
options={{
title: 'Home',
tabBarIcon: ({ color, size }) => (
<Ionicons name="home" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="search"
options={{
title: 'Search',
tabBarIcon: ({ color, size }) => (
<Ionicons name="search" size={size} color={color} />
),
}}
/>
<Tabs.Screen
name="profile"
options={{
title: 'Profile',
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
}}
/>
</Tabs>
)
}Tabs ์ปดํฌ๋ํธ๋ (tabs) ํด๋ ๋ด์ ํ์ผ๋ค์ ์๋์ผ๋ก ํญ ํ๋ฉด์ผ๋ก ์ธ์ํฉ๋๋ค. screenOptions๋ฅผ ํตํด ํญ ๋ฐ์ ์ ์ฒด์ ์ธ ์คํ์ผ๋ง์ ์ ์ดํ๊ณ , ๊ฐ Tabs.Screen์์ ๊ฐ๋ณ ํญ์ ์์ด์ฝ๊ณผ ์ ๋ชฉ์ ์ค์ ํฉ๋๋ค. tabBarIcon ์์ฑ์ ๋ ๋ ํจ์๋ฅผ ๋ฐ์ ํ์ฌ ํ์ฑ ์ํ์ ๋ฐ๋ผ ์ ์ ํ ์์์ ์์ด์ฝ์ ํ์ํฉ๋๋ค.
ํญ ๋ด๋น๊ฒ์ด์
์ ์ฅ์ ์ (tabs) ๋ผ์ฐํธ ๊ทธ๋ฃน์ด URL ๊ฒฝ๋ก์ ํฌํจ๋์ง ์๋๋ค๋ ๊ฒ์
๋๋ค. ๋ฐ๋ผ์ app/(tabs)/home.tsx ํ์ผ์ ์ค์ ๊ฒฝ๋ก๋ /home์ด ๋์ด, ๊น๋ํ URL ๊ตฌ์กฐ๋ฅผ ์ ์งํ ์ ์์ต๋๋ค. ์ด๋ ๋ฅ ๋งํน์ด๋ ์น ํ๋ซํผ์์์ URL ๊ณต์ ์ ํนํ ์ ์ฉํฉ๋๋ค.
๋์ ๋ผ์ฐํธ์ ๋ผ์ฐํธ ํ๋ผ๋ฏธํฐ
์ค์ ์ฑ์์๋ ์ํ ์์ธ ํ์ด์ง, ์ฌ์ฉ์ ํ๋กํ ๋ฑ ๋์ ์ธ ๋ฐ์ดํฐ์ ๋ฐ๋ผ ํ๋ฉด์ด ๋ณํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋๋ถ๋ถ์ ๋๋ค. Expo Router๋ ๋๊ดํธ ํ๊ธฐ๋ฒ์ ์ฌ์ฉํ์ฌ ๋์ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ๋ฅผ ์ง์ํฉ๋๋ค.
import { View, Text, StyleSheet } from 'react-native'
import { useLocalSearchParams, Stack } from 'expo-router'
export default function ProductScreen() {
// Extract the dynamic parameter from the URL
const { id } = useLocalSearchParams<{ id: string }>()
return (
<View style={styles.container}>
<Stack.Screen options={{ title: `Product ${id}` }} />
<Text style={styles.heading}>Product Details</Text>
<Text style={styles.id}>ID: {id}</Text>
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
heading: { fontSize: 24, fontWeight: 'bold', marginBottom: 8 },
id: { fontSize: 16, color: '#888' },
})[id] ํด๋ ๋๋ ํ์ผ๋ช
์ ํด๋น ์ธ๊ทธ๋จผํธ๊ฐ ๋์ ํ๋ผ๋ฏธํฐ์์ ๋ํ๋
๋๋ค. useLocalSearchParams ํ
์ ํตํด ํ์ฌ ํ๋ฉด์ ๋ผ์ฐํธ ํ๋ผ๋ฏธํฐ์ ์ ๊ทผํ ์ ์์ผ๋ฉฐ, ์ ๋ค๋ฆญ ํ์
์ ์ง์ ํ๋ฉด TypeScript์ ํ์
์ถ๋ก ํํ๋ ๋๋ฆด ์ ์์ต๋๋ค. Stack.Screen์ options๋ฅผ ์ปดํฌ๋ํธ ๋ด๋ถ์์ ๋์ ์ผ๋ก ์ค์ ํ์ฌ ํ๋ผ๋ฏธํฐ ๊ฐ์ ๋ฐ๋ผ ํค๋ ์ ๋ชฉ์ ๋ณ๊ฒฝํ๋ ๊ฒ๋ ๊ฐ๋ฅํฉ๋๋ค.
๋ ๋ณต์กํ URL ํจํด์ด ํ์ํ ๊ฒฝ์ฐ์๋ ์บ์น์ฌ(catch-all) ๋ผ์ฐํธ๋ฅผ ํ์ฉํ ์ ์์ต๋๋ค. [...slug] ํ๊ธฐ๋ฒ์ ์ฌ์ฉํ๋ฉด ์ฌ๋ฌ ์ธ๊ทธ๋จผํธ๋ฅผ ๋ฐฐ์ด๋ก ์บก์ฒํ ์ ์์ต๋๋ค.
import { useLocalSearchParams } from 'expo-router'
export default function DocsScreen() {
// /docs/getting-started/installation โ slug = ['getting-started', 'installation']
const { slug } = useLocalSearchParams<{ slug: string[] }>()
return <DocViewer path={slug.join('/')} />
}์บ์น์ฌ ๋ผ์ฐํธ๋ ๋ฌธ์ ๋ทฐ์ด, ์ฝํ
์ธ ๊ด๋ฆฌ ์์คํ
, ์ค์ฒฉ๋ ์นดํ
๊ณ ๋ฆฌ ๊ตฌ์กฐ ๋ฑ ๊น์ด๊ฐ ์ ๋์ ์ธ ๊ฒฝ๋ก๋ฅผ ์ฒ๋ฆฌํด์ผ ํ ๋ ํนํ ์ ์ฉํฉ๋๋ค. /docs/getting-started/installation ๊ฒฝ๋ก๋ก ์ ๊ทผํ๋ฉด slug ํ๋ผ๋ฏธํฐ์ ['getting-started', 'installation'] ๋ฐฐ์ด์ด ์ ๋ฌ๋ฉ๋๋ค.
React Native ๋ฉด์ ์ค๋น๊ฐ ๋์ จ๋์?
์ธํฐ๋ํฐ๋ธ ์๋ฎฌ๋ ์ดํฐ, flashcards, ๊ธฐ์ ํ ์คํธ๋ก ์ฐ์ตํ์ธ์.
ํ์ ์์ ๋ผ์ฐํ (Typed Routes)
๋๊ท๋ชจ ์ ํ๋ฆฌ์ผ์ด์ ์์๋ ์กด์ฌํ์ง ์๋ ๊ฒฝ๋ก๋ก์ ๋ด๋น๊ฒ์ด์ ์ด ๋ฐํ์ ์ค๋ฅ๋ฅผ ์ ๋ฐํ ์ ์์ต๋๋ค. Expo Router์ ํ์ ์์ ๋ผ์ฐํ ๊ธฐ๋ฅ์ ํ์ฑํํ๋ฉด ์ปดํ์ผ ํ์์ ์๋ชป๋ ๊ฒฝ๋ก ์ฐธ์กฐ๋ฅผ ๊ฐ์งํ ์ ์์ด, ์ด๋ฌํ ์ค๋ฅ๋ฅผ ์ฌ์ ์ ๋ฐฉ์งํ ์ ์์ต๋๋ค.
๋จผ์ app.json์์ ์คํ์ ๊ธฐ๋ฅ์ ํ์ฑํํฉ๋๋ค.
{
"expo": {
"experiments": {
"typedRoutes": true
}
}
}์ด ์ค์ ์ ํ์ฑํํ๋ฉด Expo CLI๊ฐ app/ ๋๋ ํ ๋ฆฌ์ ํ์ผ ๊ตฌ์กฐ๋ฅผ ๋ถ์ํ์ฌ ํ์
์ ์ ํ์ผ์ ์๋์ผ๋ก ์์ฑํฉ๋๋ค. ์ดํ router.push(), router.replace(), Link ์ปดํฌ๋ํธ ๋ฑ์์ ์ฌ์ฉํ๋ ๋ชจ๋ ๊ฒฝ๋ก ๋ฌธ์์ด์ ๋ํด TypeScript๊ฐ ์ ํจ์ฑ์ ๊ฒ์ฆํฉ๋๋ค.
import { router } from 'expo-router'
function handleCheckout(cartId: string) {
// TypeScript validates this route exists
router.push(`/product/${cartId}`)
// This would cause a compile error if /nonexistent doesn't exist
// router.push('/nonexistent')
}ํ์ ์์ ๋ผ์ฐํ ์ ํนํ ํ ํ๋ก์ ํธ์์ ํฐ ํจ๊ณผ๋ฅผ ๋ฐํํฉ๋๋ค. ํ ๊ฐ๋ฐ์๊ฐ ํ์ผ๋ช ์ ๋ณ๊ฒฝํ๊ฑฐ๋ ๊ฒฝ๋ก ๊ตฌ์กฐ๋ฅผ ๋ฆฌํฉํ ๋งํ ๋, ํด๋น ๊ฒฝ๋ก๋ฅผ ์ฐธ์กฐํ๋ ๋ชจ๋ ์ฝ๋์์ ์ฆ์ ์ปดํ์ผ ์๋ฌ๊ฐ ๋ฐ์ํ๋ฏ๋ก ๋๋ฝ๋ ์์ ์ฌํญ์ ๋น ๋ฅด๊ฒ ๋ฐ๊ฒฌํ ์ ์์ต๋๋ค. ์ฝ๋ ๋ฆฌ๋ทฐ ๊ณผ์ ์์๋ ์๋ชป๋ ๊ฒฝ๋ก ์ฐธ์กฐ๋ฅผ ์๋์ผ๋ก ๊ฒ์ถํ์ฌ ํ์ง์ ๋ณด์ฅํฉ๋๋ค.
๋ชจ๋ฌ ํ๋ฉด๊ณผ ํ๋ ์ ํ ์ด์ ์ค์
๋ชจ๋ฌ์ ํ์ฌ ์ปจํ
์คํธ๋ฅผ ์ ์งํ๋ฉด์ ์ถ๊ฐ ์ ๋ณด๋ฅผ ์
๋ ฅ๋ฐ๊ฑฐ๋ ํ์ํด์ผ ํ ๋ ์์ฃผ ์ฌ์ฉ๋๋ UI ํจํด์
๋๋ค. Expo Router์์๋ Stack.Screen์ presentation ์ต์
์ 'modal'๋ก ์ค์ ํ๋ ๊ฒ๋ง์ผ๋ก ๋ชจ๋ฌ ํ๋ฉด์ ๊ตฌํํ ์ ์์ต๋๋ค.
import { Stack } from 'expo-router'
export default function RootLayout() {
return (
<Stack>
<Stack.Screen name="index" />
<Stack.Screen name="(tabs)" options={{ headerShown: false }} />
{/* Modal screen slides up from the bottom */}
<Stack.Screen
name="create-post"
options={{
presentation: 'modal',
headerTitle: 'New Post',
}}
/>
</Stack>
)
}๋ชจ๋ฌ๋ก ํ์๋ ํ๋ฉด์ ๊ตฌํ์ ์ผ๋ฐ ํ๋ฉด๊ณผ ๋์ผํฉ๋๋ค. ์ฐจ์ด์ ์ ๋ ์ด์์ ํ์ผ์์ ํ๋ ์ ํ
์ด์
๋ฐฉ์๋ง ์ง์ ํ๋ค๋ ๊ฒ์
๋๋ค. router.back()์ ํธ์ถํ๋ฉด ๋ชจ๋ฌ์ด ๋ซํ๋ฉฐ ์ด์ ํ๋ฉด์ผ๋ก ์์ฐ์ค๋ฝ๊ฒ ๋ณต๊ทํฉ๋๋ค.
import { View, TextInput, Button, StyleSheet } from 'react-native'
import { router } from 'expo-router'
import { useState } from 'react'
export default function CreatePostModal() {
const [title, setTitle] = useState('')
const handleSubmit = () => {
// Submit logic here
router.back() // Dismiss the modal
}
return (
<View style={styles.container}>
<TextInput
style={styles.input}
placeholder="Post title"
value={title}
onChangeText={setTitle}
/>
<Button title="Publish" onPress={handleSubmit} />
</View>
)
}
const styles = StyleSheet.create({
container: { flex: 1, padding: 24 },
input: {
borderWidth: 1,
borderColor: '#333',
borderRadius: 8,
padding: 12,
fontSize: 16,
marginBottom: 16,
},
})๋ชจ๋ฌ ํ๋ฉด์ iOS์์ ๊ธฐ๋ณธ์ ์ผ๋ก ํ๋จ์์ ์๋ก ์ฌ๋ผ์ด๋๋๋ ์ ๋๋ฉ์ด์
์ผ๋ก ํ์๋๋ฉฐ, Android์์๋ ํ๋ซํผ ๊ธฐ๋ณธ ์ ํ ์ ๋๋ฉ์ด์
์ด ์ ์ฉ๋ฉ๋๋ค. presentation ์ต์
์๋ 'modal' ์ธ์๋ 'transparentModal', 'containedModal', 'fullScreenModal' ๋ฑ ๋ค์ํ ํ๋ ์ ํ
์ด์
์คํ์ผ์ ์ง์ ํ ์ ์์ด ๋์์ธ ์๊ตฌ์ฌํญ์ ๋ง๋ ํ๋ฉด ์ ํ์ ์ ์ฐํ๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค.
ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ ๋ด๋น๊ฒ์ด์ ๊ณผ Router API
์ ์ธ์ ์ธ Link ์ปดํฌ๋ํธ ์ธ์๋, Expo Router๋ router ๊ฐ์ฒด๋ฅผ ํตํด ๋ช
๋ น์ (ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์) ๋ด๋น๊ฒ์ด์
์ ์ง์ํฉ๋๋ค. ํผ ์ ์ถ ํ ๋ฆฌ๋ค์ด๋ ํธ, ์กฐ๊ฑด๋ถ ๋ด๋น๊ฒ์ด์
, ์ฌ์ฉ์ ์ธํฐ๋์
์ ๋ฐ๋ฅธ ๋์ ํ๋ฉด ์ ํ ๋ฑ์ ์๋๋ฆฌ์ค์์ router API๊ฐ ํ์์ ์ผ๋ก ํ์ฉ๋ฉ๋๋ค.
import { router } from 'expo-router'
// Push a new screen onto the stack
router.push('/profile/settings')
// Replace the current screen (no back button)
router.replace('/login')
// Go back to the previous screen
router.back()
// Navigate with parameters
router.push({
pathname: '/product/[id]',
params: { id: '42', source: 'recommendations' },
})
// Check if going back is possible
import { useRouter } from 'expo-router'
function BackButton() {
const router = useRouter()
return router.canGoBack() ? (
<Button title="Back" onPress={() => router.back()} />
) : null
}router.replace()์ <Redirect /> ์ปดํฌ๋ํธ๋ ๋ชจ๋ ํ์ฌ ํ๋ฉด์ ๋์ฒดํ์ง๋ง, ์ฌ์ฉ ๋งฅ๋ฝ์ด ๋ค๋ฆ
๋๋ค. router.replace()๋ ์ด๋ฒคํธ ํธ๋ค๋ฌ๋ ๋น๋๊ธฐ ๋ก์ง ๋ด์์ ๋ช
๋ น์ ์ผ๋ก ์ฌ์ฉํ๊ณ , <Redirect href="/login" />์ ๋ ๋๋ง ์์ ์ ์ ์ธ์ ์ผ๋ก ์ฌ์ฉํฉ๋๋ค. ์ธ์ฆ ๊ฐ๋์ฒ๋ผ ๋ ๋๋ง ์กฐ๊ฑด์ ๋ฐ๋ผ ๋ฆฌ๋ค์ด๋ ํธํด์ผ ํ๋ ๊ฒฝ์ฐ์๋ <Redirect /> ์ปดํฌ๋ํธ๊ฐ ๋ ์ ํฉํฉ๋๋ค.
router.push()๋ ๋ด๋น๊ฒ์ด์
์คํ์ ์ ํ๋ฉด์ ์ถ๊ฐํ๋ฏ๋ก ์ฌ์ฉ์๊ฐ ๋ค๋ก๊ฐ๊ธฐ๋ฅผ ํตํด ์ด์ ํ๋ฉด์ผ๋ก ๋์๊ฐ ์ ์์ต๋๋ค. ๋ฐ๋ฉด router.replace()๋ ํ์ฌ ํ๋ฉด์ ๋์ฒดํ๋ฏ๋ก ๋ค๋ก๊ฐ๊ธฐ ํ์คํ ๋ฆฌ์ ๋จ์ง ์์ต๋๋ค. ์ด ์ฐจ์ด๋ ๋ก๊ทธ์ธ ํ ๋์๋ณด๋๋ก ์ด๋ํ๋ ๊ฒฝ์ฐ์ฒ๋ผ, ์ด์ ํ๋ฉด์ผ๋ก์ ๋ณต๊ท๊ฐ ๋
ผ๋ฆฌ์ ์ผ๋ก ๋ง์ง ์๋ ํ๋ก์ฐ์์ ์ค์ํฉ๋๋ค.
router.canGoBack() ๋ฉ์๋๋ ํ์ฌ ๋ด๋น๊ฒ์ด์
์คํ์์ ๋ค๋ก๊ฐ๊ธฐ๊ฐ ๊ฐ๋ฅํ์ง ํ์ธํ๋ ๋ฐ ์ ์ฉํฉ๋๋ค. ์ฑ์ ์ต์์ ํ๋ฉด์์๋ ๋ค๋ก๊ฐ๊ธฐ ๋ฒํผ์ ์จ๊ธฐ๋ ๋ฑ ์กฐ๊ฑด๋ถ UI ๋ ๋๋ง์ ํ์ฉํ ์ ์์ต๋๋ค.
๋ฏธ๋ค์จ์ด์ ๋ผ์ฐํธ ๋ณดํธ
์ค์ ํ๋ก๋์ ์ฑ์์๋ ํน์ ํ๋ฉด์ ๋ํ ์ ๊ทผ์ ์ธ์ฆ ์ํ์ ๋ฐ๋ผ ์ ํํด์ผ ํ๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. Expo Router๋ ์๋ฒ ์ธก ๋ฏธ๋ค์จ์ด์ ํด๋ผ์ด์ธํธ ์ธก ๋ ์ด์์ ๊ฐ๋ ๋ ๊ฐ์ง ๋ฐฉ์์ผ๋ก ๋ผ์ฐํธ ๋ณดํธ๋ฅผ ์ง์ํฉ๋๋ค.
์๋ฒ ์ธก ๋ฏธ๋ค์จ์ด๋ +middleware.ts ํ์ผ์ ํตํด ๊ตฌํํฉ๋๋ค. ์์ฒญ์ด ๋ผ์ฐํธ์ ๋๋ฌํ๊ธฐ ์ ์ ์ธํฐ์
ํธํ์ฌ ์ธ์ฆ ํ ํฐ ๊ฒ์ฆ, ๋ฆฌ๋ค์ด๋ ํธ ๋ฑ์ ๋ก์ง์ ์ํํ ์ ์์ต๋๋ค.
import { type MiddlewareRequest } from 'expo-router/server'
export function middleware(request: MiddlewareRequest) {
const { pathname } = request.nextUrl
// Protect dashboard routes
const protectedPaths = ['/dashboard', '/settings', '/profile']
const isProtected = protectedPaths.some(p => pathname.startsWith(p))
if (isProtected) {
const token = request.cookies.get('session')
if (!token) {
return Response.redirect(new URL('/login', request.url))
}
}
return undefined // Continue to route
}์๋ฒ ๋ฏธ๋ค์จ์ด(+middleware.ts)๋ ์น ํ๋ซํผ์์๋ง ์คํ๋ฉ๋๋ค. iOS ๋ฐ Android ๋ค์ดํฐ๋ธ ํ๊ฒฝ์์๋ ์๋ฒ๊ฐ ์กด์ฌํ์ง ์์ผ๋ฏ๋ก ๋ฏธ๋ค์จ์ด๊ฐ ๋์ํ์ง ์์ต๋๋ค. ๋ค์ดํฐ๋ธ ํ๋ซํผ์์ ๋ผ์ฐํธ ๋ณดํธ๊ฐ ํ์ํ ๊ฒฝ์ฐ์๋ ๋ฐ๋์ ํด๋ผ์ด์ธํธ ์ธก ๋ ์ด์์ ๊ฐ๋๋ฅผ ํจ๊ป ๊ตฌํํด์ผ ํฉ๋๋ค.
ํด๋ผ์ด์ธํธ ์ธก์์๋ ๋ ์ด์์ ํ์ผ ๋ด์์ ์ธ์ฆ ์ํ๋ฅผ ํ์ธํ๊ณ , ๋ฏธ์ธ์ฆ ์ฌ์ฉ์๋ฅผ ๋ฆฌ๋ค์ด๋ ํธํ๋ ํจํด์ด ๊ฐ์ฅ ์ผ๋ฐ์ ์ ๋๋ค. ๋ผ์ฐํธ ๊ทธ๋ฃน์ ํ์ฉํ๋ฉด ๋ณดํธ๊ฐ ํ์ํ ํ๋ฉด๋ค์ ๋ ผ๋ฆฌ์ ์ผ๋ก ๋ฌถ์ด ๊ด๋ฆฌํ ์ ์์ต๋๋ค.
import { Redirect, Stack } from 'expo-router'
import { useAuth } from '@/hooks/useAuth'
export default function AuthenticatedLayout() {
const { isLoggedIn, isLoading } = useAuth()
if (isLoading) return null
if (!isLoggedIn) return <Redirect href="/login" />
return <Stack />
}์ด ํจํด์์ (authenticated) ๋ผ์ฐํธ ๊ทธ๋ฃน ๋ด์ ๋ชจ๋ ํ๋ฉด์ AuthenticatedLayout์ ๊ฑฐ์น๊ฒ ๋ฉ๋๋ค. ์ธ์ฆ ์ํ ํ์ธ ์ค์๋ null์ ๋ฐํํ์ฌ ๊น๋นก์์ ๋ฐฉ์งํ๊ณ , ๋ฏธ์ธ์ฆ ์ํ์์๋ <Redirect /> ์ปดํฌ๋ํธ๋ฅผ ํตํด ๋ก๊ทธ์ธ ํ๋ฉด์ผ๋ก ์ฆ์ ๋ฆฌ๋ค์ด๋ ํธํฉ๋๋ค. ์ธ์ฆ์ด ์๋ฃ๋ ์ฌ์ฉ์์๊ฒ๋ง <Stack />์ ํตํด ํ์ ํ๋ฉด๋ค์ด ๋ ๋๋ง๋ฉ๋๋ค.
์๋ฒ ๋ฏธ๋ค์จ์ด์ ํด๋ผ์ด์ธํธ ๋ ์ด์์ ๊ฐ๋๋ฅผ ํจ๊ป ์ฌ์ฉํ๋ฉด ์น๊ณผ ๋ค์ดํฐ๋ธ ํ๋ซํผ ๋ชจ๋์์ ๊ฒฌ๊ณ ํ ์ธ์ฆ ์ฒด๊ณ๋ฅผ ๊ตฌ์ถํ ์ ์์ต๋๋ค. ์๋ฒ ๋ฏธ๋ค์จ์ด๊ฐ ์น์์์ 1์ฐจ ๋ฐฉ์ด์ ์ญํ ์ ํ๊ณ , ํด๋ผ์ด์ธํธ ๊ฐ๋๊ฐ ๋ชจ๋ ํ๋ซํผ์์์ ์ต์ข ๋ณดํธ ์ญํ ์ ์ํํฉ๋๋ค.
๊ฒฐ๋ก
Expo Router๋ React Native ์ฑ์ ๋ด๋น๊ฒ์ด์ ์ ํ์ผ ์์คํ ๊ธฐ๋ฐ์ผ๋ก ๊ทผ๋ณธ์ ์ผ๋ก ๋จ์ํํฉ๋๋ค. ์ด ๊ฐ์ด๋์์ ๋ค๋ฃฌ ํต์ฌ ๋ด์ฉ์ ์ ๋ฆฌํ๋ฉด ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ํ์ผ ๊ธฐ๋ฐ ๋ผ์ฐํ
์
app/๋๋ ํ ๋ฆฌ์ ํ์ผ ๊ตฌ์กฐ๊ฐ ๊ณง ์ฑ์ URL ์ฒด๊ณ๊ฐ ๋๋ ์ง๊ด์ ์ธ ํจ๋ฌ๋ค์์ ์ ๊ณตํฉ๋๋ค. - ๋ ์ด์์ ํ์ผ(
_layout.tsx)์ ํตํด Stack, Tabs ๋ฑ์ ๋ค๋น๊ฒ์ดํฐ๋ฅผ ๊ฐ ๋ผ์ฐํธ ์ธ๊ทธ๋จผํธ์ ์ ์ฉํ ์ ์์ต๋๋ค. - ๋ผ์ฐํธ ๊ทธ๋ฃน(๊ดํธ ํด๋)์ URL์ ์ํฅ์ ์ฃผ์ง ์์ผ๋ฉด์ ๊ด๋ จ ํ๋ฉด๋ค์ ๋ ผ๋ฆฌ์ ์ผ๋ก ๊ตฌ์ฑํ๋ ๋ฐ ํ์ฉ๋ฉ๋๋ค.
- ๋์ ๋ผ์ฐํธ์ ์บ์น์ฌ ๋ผ์ฐํธ๋ ํ๋ผ๋ฏธํฐ ๊ธฐ๋ฐ์ ์ ์ฐํ ํ๋ฉด ๊ตฌ์ฑ์ ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
- ํ์ ์์ ๋ผ์ฐํ ์ ํ์ฑํํ๋ฉด ์๋ชป๋ ๊ฒฝ๋ก ์ฐธ์กฐ๋ฅผ ์ปดํ์ผ ํ์์ ๊ฐ์งํ์ฌ ๋ฐํ์ ์ค๋ฅ๋ฅผ ์๋ฐฉํ ์ ์์ต๋๋ค.
- ๋ชจ๋ฌ ํ๋ ์ ํ ์ด์ ์ ๋ ์ด์์ ์ค์ ๋ง์ผ๋ก ๊ฐํธํ๊ฒ ๊ตฌํ๋๋ฉฐ, ๋ค์ํ ํ์ ์คํ์ผ์ ์ง์ํฉ๋๋ค.
- Router API๋
push,replace,back๋ฑ์ ๋ฉ์๋๋ฅผ ํตํด ํ๋ก๊ทธ๋๋ฐ ๋ฐฉ์์ ์ธ๋ฐํ ๋ด๋น๊ฒ์ด์ ์ ์ด๋ฅผ ์ ๊ณตํฉ๋๋ค. - ๋ผ์ฐํธ ๋ณดํธ๋ ์๋ฒ ๋ฏธ๋ค์จ์ด์ ํด๋ผ์ด์ธํธ ๋ ์ด์์ ๊ฐ๋์ ์กฐํฉ์ผ๋ก ๋ชจ๋ ํ๋ซํผ์์ ์์ ํ ์ธ์ฆ ์ฒด๊ณ๋ฅผ ๊ตฌํํ ์ ์์ต๋๋ค.
Expo Router๋ฅผ ๋์ ํ๋ฉด ๋ด๋น๊ฒ์ด์ ์ค์ ์ ์์๋๋ ์๊ฐ์ ๋ํญ ์ค์ด๊ณ , ์ฑ์ ํต์ฌ ๊ธฐ๋ฅ ๊ฐ๋ฐ์ ์ง์คํ ์ ์์ต๋๋ค. ํ์ผ ๊ตฌ์กฐ์ ๋ณ๊ฒฝ๋ง์ผ๋ก ๋ด๋น๊ฒ์ด์ ์ ๋ฆฌํฉํ ๋งํ ์ ์์ด ํ๋ก์ ํธ์ ์ ์ง๋ณด์์ฑ๋ ํฌ๊ฒ ํฅ์๋ฉ๋๋ค.
์ฐ์ต์ ์์ํ์ธ์!
๋ฉด์ ์๋ฎฌ๋ ์ดํฐ์ ๊ธฐ์ ํ ์คํธ๋ก ์ง์์ ํ ์คํธํ์ธ์.
ํ๊ทธ
๊ณต์
๊ด๋ จ ๊ธฐ์ฌ

React Native 0.84์ Hermes V1: ์ฑ๋ฅ ์ต์ ํ, ํ๋ฆฌ์ปดํ์ผ ๋ฐ์ดํธ์ฝ๋, ๊ธฐ์ ๋ฉด์ ๊ฐ์ด๋
Hermes V1์ด React Native 0.84์ ๊ธฐ๋ณธ JavaScript ์์ง์ผ๋ก ์ฑํ๋์ด ๋ฐ์ดํธ์ฝ๋ ํ๋ฆฌ์ปดํ์ผ, Hades ๊ฐ๋น์ง ์ปฌ๋ ํฐ, ๋ฉ๋ชจ๋ฆฌ ์ต์ ํ๋ฅผ ํตํด ํ๊ธฐ์ ์ธ ์ฑ๋ฅ ํฅ์์ ์ ๊ณตํฉ๋๋ค. ๊ธฐ์ ๋ฉด์ ์์ ์์ฃผ ์ถ์ ๋๋ Hermes ๋ด๋ถ ๊ตฌ์กฐ๋ฅผ ์์ธํ ๋ค๋ฃน๋๋ค.

React Native 0.86 ์๋ฒฝ ๊ฐ์ด๋ 2026: Android 15 ์ฃ์งํฌ์ฃ์ง ์ง์๊ณผ DevTools ํ์ฉ๋ฒ
React Native 0.86์ Android 15 ์ฃ์งํฌ์ฃ์ง ์ง์, DevTools ๊ฐ์ ์ฌํญ, ๋ฉด์ ๋๋น ์ง๋ฌธ์ ์์ธํ ๋ค๋ฃน๋๋ค. ์ค์ฉ์ ์ธ ์ฝ๋ ์์ ์ ์ต์ ๋ฒ ์คํธ ํ๋ํฐ์ค๋ฅผ ์๊ฐํฉ๋๋ค.

React Native์ TypeScript 2026: ํ์ ์์ ํ ์ํคํ ์ฒ์ ๋ฉด์ ์ง๋ฌธ
2026๋ React Native์์ TypeScript๋ฅผ ํ์ฉํ ํ์ ์์ ์ํคํ ์ฒ๋ฅผ ๋ค๋ฃน๋๋ค. Codegen, TurboModules, Strict TypeScript API, ํ์ ์์ ๋ด๋น๊ฒ์ด์ , ๋ฉด์ ์ง๋ฌธ์ ์ฝ๋ ์์ ์ ํจ๊ป ์ค๋ช ํฉ๋๋ค.