July 13, 202411 minutes
안녕하세요!
리액트 네이티브 강좌 12편입니다.
편의를 위해 전체 강좌 링크도 넣었습니다.
리액트 네이티브 강좌. 7편 - 동적인 사용자 인터페이스 구현하기 - Dimensions API와 플랫폼별 코드 작성법
리액트 네이티브 강좌. 9편 - Input and Forms with Switch, KeyboardAvoidingView, Form Validation
리액트 네이티브 강좌. 10편 - Networking 다루기, 데이터 fetching, loading state, error handling
** 목차 **
스택 네비게이션에 대해 알아봤으니, 이제 또 다른 필수 네비게이션, 바로 **드로어 네비게이션(Drawer Navigation)**을 살펴보겠습니다.
스택 네비게이터가 화면을 차곡차곡 쌓아 올리는 방식이라면, 드로어 네비게이터는 화면 측면에서 스윽 나타나는 숨겨진 메뉴라고 생각하면 됩니다.
먼저 프로젝트에 드로어 네비게이션 패키지를 설치해야겠죠?
리액트 네비게이션 공식 문서를 참고하면 쉽게 따라 할 수 있습니다.
npm install @react-navigation/drawer
다음으로 드로어 네비게이터가 필요로 하는 라이브러리인 react-native-gesture-handler
와 react-native-reanimated
를 설치하면 됩니다.
npm install react-native-gesture-handler react-native-reanimated
이제 메인 코드를 아래 코드와 같이 구성합니다.
import { NavigationContainer } from '@react-navigation/native';
import { createDrawerNavigator } from '@react-navigation/drawer';
const Drawer = createDrawerNavigator(); // 드로어 네비게이터 인스턴스 생성
export default function App() {
return (
<NavigationContainer independent={true}>
<Drawer.Navigator>
{/* 여기에 드로어 메뉴에 포함될 화면을 추가할 거예요! */}
</Drawer.Navigator>
</NavigationContainer>
);
}
react-native-reanimated
라이브러리 설정을 위해 babel.config.js
파일을 열고, plugins
배열에 아래 코드를 추가해 줍니다.
module.exports = function(api) {
api.cache(true);
return {
presets: ['babel-preset-expo'],
plugins: [
'react-native-reanimated/plugin', // reanimated 플러그인 추가!
],
};
};
마지막으로 package.json
파일을 열고, expo start
스크립트에 -c
옵션을 추가하여 캐시를 삭제하고 앱을 실행하도록 설정합니다.
{
// ...
"scripts": {
"start": "expo start -c", // 캐시 삭제 옵션 추가
// ...
},
// ...
}
설치 및 설정이 완료되었으니 이제 드로어 메뉴에 포함될 화면을 만들어 보겠습니다.
screens
폴더에 DashboardScreen.js
와 SettingsScreen.js
파일을 생성하고, 각각 아래 코드를 추가합니다.
// screens/DashboardScreen.js
import { View, Text, StyleSheet } from 'react-native';
const DashboardScreen = () => {
return (
<View style={styles.container}>
<Text style={styles.text}>Dashboard Screen</Text>
</View>
);
};
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
},
text: {
fontSize: 24,
fontWeight: 'bold',
},
});
export default DashboardScreen;
// screens/SettingsScreen.js
import { View, Text, StyleSheet } from 'react-native';
const SettingsScreen = () => {
return (
<View style={styles.container}>
<Text style={styles.text}>Settings Screen</Text>
</View>
);
};
// ... (스타일은 DashboardScreen과 동일)
export default SettingsScreen;
이제 App.js
파일로 돌아와서 드로어 메뉴에 방금 만든 두 화면을 추가해 줍니다.
// App.js
// ... (이전 코드와 동일)
import DashboardScreen from './screens/DashboardScreen';
import SettingsScreen from './screens/SettingsScreen';
// ...
export default function App() {
return (
<NavigationContainer>
<Drawer.Navigator>
<Drawer.Screen name="Dashboard" component={DashboardScreen} />
<Drawer.Screen name="Settings" component={SettingsScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
}
Drawer.Screen
컴포넌트의 name
prop은 드로어 메뉴에 표시될 레이블이고, component
prop은 해당 메뉴를 눌렀을 때 표시될 화면 컴포넌트를 지정합니다.
이제 서버를 다시 시작하고 앱을 실행해 봅시다.
npm run ios
화면 왼쪽 가장자리에서 스와이프하거나 왼쪽 상단의 아이콘을 눌러 드로어 메뉴를 열 수 있을 겁니다.
드로어 메뉴에서 “Dashboard"와 “Settings"를 눌러 화면을 전환할 수 있을 겁니다.
navigation
prop을 사용하여 드로어 메뉴를 프로그래밍 방식으로 열고 닫을 수도 있습니다.
DashboardScreen
에 버튼을 하나 추가하고, 버튼을 누르면 드로어 메뉴가 열리도록 해 보겠습니다.
// screens/DashboardScreen.js
// ... (이전 코드와 동일)
import { View, Text, StyleSheet, Button } from 'react-native';
const DashboardScreen = ({ navigation }) => { // navigation prop 추가!
return (
<View style={styles.container}>
<Text style={styles.text}>Dashboard Screen</Text>
<Button title="Toggle Drawer" onPress={() => navigation.toggleDrawer()} />
</View>
);
};
// ... (나머지 코드는 동일)
navigation.toggleDrawer()
메서드를 호출하면 드로어 메뉴를 열거나 닫을 수 있습니다.
앱을 실행하고 “Toggle Drawer” 버튼을 눌러 드로어 메뉴가 잘 열리는지 확인해 봅시다.
navigation.jumpTo(routeName)
메서드를 사용하면 드로어 UI 없이 특정 화면으로 바로 이동할 수도 있습니다.
// screens/DashboardScreen.js
// ... (이전 코드와 동일)
<Button title="Go to Settings" onPress={() => navigation.jumpTo('Settings')} />
// ... (나머지 코드는 동일)
이제 “Go to Settings” 버튼을 누르면 드로어 메뉴를 열지 않고 바로 Settings
화면으로 이동하는 것을 확인할 수 있을 겁니다.
기본적인 드로어 네비게이터 설정을 마쳤으니, 이제 옵션을 사용해서 좀 더 세련되게 꾸며 볼 시간이에요! 🎨 DashboardScreen
컴포넌트에 options
prop을 사용하여 다양한 옵션을 지정해 보도록 할게요.
먼저 title
옵션을 사용하여 화면 제목을 변경해 볼까요?
<Drawer.Screen
name="Dashboard"
component={DashboardScreen}
options={{
title: "My Dashboard",
}}
/>
앱을 실행해 보면 헤더 제목과 드로어 메뉴 레이블이 모두 “My Dashboard"로 변경된 것을 확인할 수 있을 겁니다.
헤더 제목과 드로어 메뉴 레이블을 다르게 설정하고 싶다면 drawerLabel
옵션을 사용하면 됩니다.
<Drawer.Screen
name="Dashboard"
component={DashboardScreen}
options={{
title: "My Dashboard",
drawerLabel: "Dashboard Label",
}}
/>
이제 헤더 제목은 “My Dashboard"로 유지되고, 드로어 메뉴에는 “Dashboard Label"이라는 레이블이 표시될 겁니다.
드로어 메뉴에서 현재 활성화된 아이템의 스타일을 변경하고 싶다면 drawerActiveTintColor
와 drawerActiveBackgroundColor
옵션을 사용하면 됩니다.
<Drawer.Screen
name="Dashboard"
component={DashboardScreen}
options={{
title: "My Dashboard",
drawerLabel: "Dashboard Label",
drawerActiveTintColor: "#fff", // 활성 텍스트 색상
drawerActiveBackgroundColor: "#6200EE", // 활성 배경 색상
}}
/>
이제 앱을 실행하고 드로어 메뉴를 열어보면 “Dashboard Label” 아이템의 텍스트 색상이 흰색으로, 배경색은 보라색으로 변경된 것을 확인할 수 있을 겁니다.
드로어 메뉴 전체의 스타일을 변경하고 싶다면 drawerContentStyle
옵션을 사용하면 됩니다.
<Drawer.Screen
name="Dashboard"
component={DashboardScreen}
options={{
title: "My Dashboard",
drawerLabel: "Dashboard Label",
drawerActiveTintColor: "#fff", // 활성 텍스트 색상
drawerActiveBackgroundColor: "#6200EE", // 활성 배경 색상
drawerContentStyle: {
backgroundColor: "#c6cbff", // 드로어 메뉴 배경 색상
},
}}
/>
이제 앱을 실행해 보면 드로어 메뉴의 배경색이 연한 보라색으로 변경된 것을 확인할 수 있을 겁니다.
스택 네비게이션과 드로어 네비게이션에 이어서 이번에는 **탭 네비게이션(Tab Navigation)**을 알아보겠습니다.
탭 네비게이션은 주로 화면 하단에 표시되는 탭을 눌러서 여러 화면을 전환하는 방식을 제공하는데요, 많은 앱에서 사용되는 흔하고 직관적인 네비게이션 패턴입니다.
먼저 프로젝트에 @react-navigation/bottom-tabs
라이브러리를 설치해야합니다.
npm install @react-navigation/bottom-tabs
이제 아래 코드로 바꿉니다.
import { NavigationContainer } from '@react-navigation/native';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
const Tab = createBottomTabNavigator(); // 탭 네비게이터 인스턴스 생성
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator>
{/* 여기에 탭으로 표시될 화면을 추가할 거예요! */}
</Tab.Navigator>
</NavigationContainer>
);
}
이전 예제에서 만들었던 SettingsScreen
컴포넌트를 재사용하고, ProfileScreen
과 CourseListScreen
컴포넌트를 새로 만들어서 탭에 추가해 볼게요. screens
폴더에 ProfileScreen.js
와 CourseListScreen.js
파일을 생성하고 각각 아래 코드를 추가합니다.
// screens/ProfileScreen.js
import { View, Text, StyleSheet } from 'react-native';
const ProfileScreen = () => {
return (
<View style={styles.container}>
<Text style={styles.text}>Profile Screen</Text>
</View>
);
};
// ... (스타일은 이전 예제와 동일)
export default ProfileScreen;
// screens/CourseListScreen.js
import { View, Text, StyleSheet } from 'react-native';
const CourseListScreen = () => {
return (
<View style={styles.container}>
<Text style={styles.text}>Course List Screen</Text>
</View>
);
};
// ... (스타일은 이전 예제와 동일)
export default CourseListScreen;
이제 App.js
파일로 돌아와서 Tab.Navigator
안에 방금 만든 화면들을 추가해 줍니다.
// App.js
// ... (이전 코드와 동일)
import ProfileScreen from './screens/ProfileScreen';
import CourseListScreen from './screens/CourseListScreen';
import SettingsScreen from './screens/SettingsScreen';
// ...
export default function App() {
return (
<NavigationContainer>
<Tab.Navigator>
<Tab.Screen name="Course List" component={CourseListScreen} />
<Tab.Screen name="Profile" component={ProfileScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
</NavigationContainer>
);
}
Tab.Screen
컴포넌트의 name
prop은 탭에 표시될 레이블이고, component
prop은 해당 탭을 눌렀을 때 표시될 화면 컴포넌트를 지정합니다.
이제 서버를 다시 시작하고 앱을 실행해 보세요!
npm run ios
화면 하단에 3개의 탭이 생성된 것을 확인할 수 있을 겁니다.
각 탭을 눌러서 해당 화면으로 이동해 보십시요.
이번에는 탭 네비게이션을 좀 더 개성 있게 꾸밀 수 있는 다양한 옵션들을 살펴보도록 하겠습니다.
먼저 Tab.Navigator
컴포넌트에 screenOptions
prop을 사용하여 탭 네비게이터 전체에 적용될 옵션들을 설정해 보겠습니다.
// App.js
// ... (이전 코드와 동일)
<Tab.Navigator screenOptions={{ /* 옵션 설정 시작 */ }}>
{/* ... */}
</Tab.Navigator>
tabBarLabelPosition
옵션을 사용하면 탭 라벨의 위치를 아이콘 위 또는 아래로 변경할 수 있습니다.
기본값은 'below-icon'
으로, 아이콘 아래에 라벨이 표시됩니다.
'beside-icon'
으로 설정하면 아이콘 오른쪽에 라벨이 표시됩니다.
일반적으로 모바일 기기에서는 'below-icon'
, 태블릿에서는 'beside-icon'
을 많이 사용합니다.
// ...
<Tab.Navigator screenOptions={{
tabBarLabelPosition: 'beside-icon', // 라벨 위치 변경
}}>
{/* ... */}
</Tab.Navigator>
tabBarShowLabel
옵션을 사용하면 탭 라벨을 표시할지 숨길지 설정할 수 있습니다.
기본값은 true
로, 라벨이 표시됩니다.
false
로 설정하면 라벨이 숨겨지고 아이콘만 표시됩니다.
// ...
<Tab.Navigator screenOptions={{
// ...
tabBarShowLabel: false, // 라벨 숨기기
}}>
{/* ... */}
</Tab.Navigator>
tabBarActiveTintColor
옵션을 사용하면 활성 탭의 텍스트 및 아이콘 색상을 변경할 수 있습니다.
// ...
<Tab.Navigator screenOptions={{
// ...
tabBarActiveTintColor: 'purple', // 활성 탭 색상 변경
}}>
{/* ... */}
</Tab.Navigator>
tabBarInactiveTintColor
옵션을 사용하면 비활성 탭의 텍스트 및 아이콘 색상을 변경할 수 있습니다.
기본값은 회색이지만, 원하는 색상으로 자유롭게 변경할 수 있습니다.
// ...
<Tab.Navigator screenOptions={{
// ...
tabBarInactiveTintColor: 'gray', // 비활성 탭 색상 변경 (기본값)
}}>
{/* ... */}
</Tab.Navigator>
이제 Tab.Screen
컴포넌트에 options
prop을 사용하여 각 탭 화면별로 옵션을 설정해 보겠습니다.
tabBarLabel
옵션을 사용하면 탭에 표시되는 라벨을 변경할 수 있습니다.
ProfileScreen
탭의 라벨을 “My Profile"로 변경해 볼까요?
물론 Tab.Navigator
의 screenOptions에서 tabBarShowLabel을 ’true’로 변경하는 걸 잊지 마시구요.
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarLabel: "My Profile", // 탭 라벨 변경
}}
/>
tabBarIcon
옵션을 사용하면 탭에 표시되는 아이콘을 변경할 수 있습니다.
@expo/vector-icons
패키지에서 제공하는 Ionicons
을 사용하여 아이콘을 변경해 보겠습니다.
// ... (이전 코드와 동일)
import { Ionicons } from '@expo/vector-icons'; // Ionicons import
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarLabel: "My Profile", // 탭 라벨 변경
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
}}
/>
tabBarIcon
옵션에 함수를 전달하면, 이 함수는 color
와 size
prop을 인자로 받아서 아이콘 컴포넌트를 반환해야 합니다.
활성 탭 색상과 비활성 탭 색상에 맞춰 아이콘 색상도 자동으로 변경됩니다!
tabBarBadge
옵션을 사용하면 탭 아이콘에 뱃지를 추가할 수 있습니다.
알림 탭이나 메시지함 탭처럼 사용자의 주의가 필요한 경우 유용하게 활용할 수 있습니다.
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarLabel: "My Profile", // 탭 라벨 변경
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
tabBarBadge: 3, // 뱃지 추가
}}
/>
이번에는 네비게이터를 중첩해서 사용하는 방법에 대해 알아보겠습니다.
네비게이터 중첩은 여러 유형의 네비게이터를 조합하여 마치 마법처럼 끊김 없고 체계적인 사용자 경험을 만들어낼 수 있는 강력한 기술입니다.
마치 여러 갈래의 작은 길로 이어진 큰 도로처럼, 각 네비게이터는 고유한 규칙을 가지면서도 서로 연결되어 유기적으로 동작하게 됩니다.
자, 그럼 코드를 보면서 리액트 네이티브 앱에서 네비게이터를 중첩하는 방법을 자세히 알아보겠습니다.
이전 예제에서 만들었던 스택 네비게이터와 탭 네비게이터를 재사용하여 중첩해 볼 겁니다.
먼저 스택 네비케이터 예제였던 AppStack.js
파일에서 작은 변경 작업을 할 겁니다.
// AppStack.js
// ... (이전 코드와 동일)
const AboutStack = () => { // AboutStack 컴포넌트 생성
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="About" component={AboutScreen} />
</Stack.Navigator>
);
};
export default AboutStack; // AboutStack 컴포넌트 export
스택 네비게이터를 AboutStack
이라는 새로운 컴포넌트로 감싸고, 이 컴포넌트를 export 하도록 변경했습니다.
이제 index.js
파일에서 AboutStack
컴포넌트를 import 하여 탭 네비게이터 안에 중첩해 볼 겁니다.
import { NavigationContainer } from "@react-navigation/native";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import ProfileScreen from "../screens/ProfileScreen";
import CourseListScreen from "../screens/CourseListScreen";
import SettingsScreen from "../screens/SettingsScreen";
import { Ionicons } from "@expo/vector-icons"; // Ionicons import
import AboutStack from "./AppStack"; // AboutStack 컴포넌트 import
const Tab = createBottomTabNavigator(); // 탭 네비게이터 인스턴스 생성
export default function App() {
return (
<NavigationContainer independent={true}>
<Tab.Navigator
screenOptions={{
tabBarLabelPosition: "beside-icon", // 라벨 위치 변경
tabBarShowLabel: true, // 라벨 숨기기
tabBarActiveTintColor: "purple", // 활성 탭 색상 변경
tabBarInactiveTintColor: "gray", // 비활성 탭 색상 변경 (기본값)
}}
>
<Tab.Screen name="Course List" component={CourseListScreen} />
<Tab.Screen
name="Profile"
component={ProfileScreen}
options={{
tabBarLabel: "My Profile", // 탭 라벨 변경
tabBarIcon: ({ color, size }) => (
<Ionicons name="person" size={size} color={color} />
),
tabBarBadge: 3, // 뱃지 추가
}}
/>
<Tab.Screen name="Settings" component={SettingsScreen} />
<Tab.Screen name="About Stack" component={AboutStack} />
</Tab.Navigator>
</NavigationContainer>
);
}
Tab.Screen
컴포넌트를 하나 추가하고, name
prop에는 “About Stack"을, component
prop에는 방금 import 한 AboutStack
컴포넌트를 지정했습니다.
이제 앱을 실행해 봅시다.
“About Stack"이라는 새로운 탭이 생겼고, 이 탭을 누르면 AboutStack
컴포넌트, 즉 스택 네비게이터가 화면에 나타나는 것을 확인할 수 있을 겁니다.
탭 네비게이터와 스택 네비게이터가 모두 헤더를 가지고 있기 때문에, 중첩된 화면에서는 헤더가 두 번 나타나는 문제가 발생할 수 있습니다.
이 경우에는 스택 네비게이터의 헤더만 표시하고 탭 네비게이터의 헤더는 숨기는 것이 좋겠죠?
Tab.Screen
컴포넌트의 options
prop을 사용하여 탭 네비게이터의 헤더를 숨길 수 있습니다.
// ... (이전 코드와 동일)
<Tab.Screen
name="About Stack"
component={AboutStack}
options={{ headerShown: false }} // 탭 네비게이터 헤더 숨기기
/>
// ... (나머지 코드는 동일)
이제 앱을 실행하면 “About Stack” 탭에서 스택 네비게이터의 헤더만 표시되는 것을 확인할 수 있을 겁니다.
끝.