react-native에서 화면 전환을 구현할 수 있도록 react-native-community에서 관리하는 라이브러리이다. 화면 전환을 위해 사용하는 라이브러리 중 가장 사용률이 높다.
설치
react-navigation이 의존하는 라이브러리가 있기 때문에 함께 설치해준다.
$ yarn add @react-navigation/native
$ yarn add react-native-screens react-native-safe-area-context
$ yarn add @react-navigation/native-stack
브라우저에는 stack 자료구조를 사용하여 구현된 History 기능이 있기 때문에 쉽게 이전 페이지로 이동할 수 있다.
react-native application에서는 History의 기능을 구현하기 위해 Native Stack Navigator를 사용하고, 이는 @react-navigation/native-stack 라이브러리를 설치하여 만들 수 있다.
화면 전환
react-navigation을 사용해 화면 전환을 하기 위해서는 react-navigation 라이브러리에서 제공하는 NavigationContainer 컴포넌트로 App 전체를 감싸야 한다.
다음으로 @react-navigation/native-stack에서 제공하는 createNativeStackNavigator 함수로 Stack 객체를 생성한다. 해당 객체에는 Stack.Navigator와 Stack.Screen 컴포넌트가 존재하는데, NavigationContainer 컴포넌트 내부에 Stack.Navigator 컴포넌트를 넣고, 그 안에 이동할 페이지들의 목록을 Stack.Screen 컴포넌트로 설정하면 화면 전환이 가능해진다.
import { NavigationContainer } from "@react-navigation/native";
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import React from 'react';
import DetailScreen from './screens/DetailScreen';
import HomeScreen from './screens/HomeScreen';
const Stack = createNativeStackNavigator();
const App = () => {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName="Home">
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
</NavigationContainer>
)
}
그렇다면 Home에서 Detail 페이지로 화면 전환할 수 있도록 onPress 함수를 작성해보자. Stack 객체를 통해 스크린으로 사용된 컴포넌트는 navigation이라는 객체를 props로 받아올 수 있다. 해당 객체의 navigate() 함수를 사용하면 다른 화면으로 넘어갈 수 있다.
navigation.navigate('Detail') // Stack.Screen 컴포넌트의 name 속성값
이 때, navigate() 함수와 동일한 역할을 하는 push() 함수도 있다. 둘의 차이점은 route parameter를 사용할 때 확인할 수 있다.
route paramete
navigation.push() 혹은 navigation.navigate()함수의 두 번째 매개변수로 의존해야 하는 객체 매개변수를 설정할 수 있다. 계정명이 seongmin인 유저의 프로필을 보고 싶다면 {username: 'seongmin'}으로 parameter를 설정하면 된다. 그러면 username, seongmin이라는 유저가 갖고 있는 데이터를 참조하여 스크린을 구성할 수 있다.
navigation.navigate() 함수는 새로 이동할 스크린이 현재의 스크린과 같으면 이동하지 않고 parameter만 변경한다. 반면 navigation.push() 함수는 새로운 parameter를 가진 스크린으로 항상 이동하기 때문에 뒤로가기 버튼을 클릭했을 때 이전의 parameter로 이루어진 스크린으로 돌아갈 수 있다.
뒤로가기
navigation 객체의 pop() 함수와 popToTop() 함수를 이용하여 뒤로가기와 같은 기능을 구현할 수 있다. navigation.pop()을 통해 이전 스크린으로 돌아갈 수 있고, navigation.popToTop() 함수를 이용하면 가장 첫 번째 화면인 홈 화면으로 돌아갈 수 있다.
헤더 커스터마이징
헤더를 커스터마이징 하는 데에는 두 가지 방법이 있다.
• Stack.Screen 컴포넌트의 options 속성 활용
// ...
<Stack.Screen
name="Home"
component={HomeScreen}
options={{
// title 지정
title: '홈',
// header style
headerStyle: {
backgroundColor: '#29b6f6',
},
headerTintColor: '#fff',
headerTitleStyle: {
fontWeight: 'bold',
fontSize: 20,
},
}}
/>
// ...
• 스크린 컴포넌트에서 navigation.setOptions() 함수 활용
useEffect(() => {
navigation.setOptions({
title: `Detail ${route.params.id}`,
});
}, [navigation, route.params.id]);
헤더의 좌측, 우측 영역에 다른 컴포넌트를 보여주고자 한다면 Stack.Screen 컴포넌트의 options 속성에서 headerLeft, headerRight를 사용할 수 있다. headerBackVisible 속성을 사용하면 뒤로가기 버튼 사용 여부도 설정할 수 있다.
<Stack.Screen
name="Detail"
component={DetailScreen}
options={{
// 뒤로가기 버튼 비활성화
headerBackVisible: false,
// header 왼쪽 컴포넌트
headerLeft: ({onPress}) => (
<TouchableOpacity onPress={onPress}>
<Text>Left</Text>
</TouchableOpacity>
),
// header title
headerTitle: ({children}) => (
<View>
<Text>{children}</Text>
</View>
),
// header 오른쪽 컴포넌트
headerRight: () => (
<View>
<Text>Right</Text>
</View>
),
}}
/>
만일 header를 보여주지 않기를 원한다면 Stack.Screen 컴포넌트의 options 속성에서 headerShown을 false로 설정하면 된다.
네이티브 스택 내비게이터가 관리하는 모든 스크린에서 동일한 속성을 적용하고자 한다면 Stack.Navigator 컴포넌트의 screenOptions 속성에서 스타일을 설정하면 된다.
<Stack.Navigator
initialRouteName="Home"
screenOptions={{
headerShown: false,
}}>
// ...
</Stack.Navigator>
react-navigation & react-native-safe-area-context
react-navigation에는react-native-safe-area-context가 내장되어 있다. 따라서 별도의 라이브러리를 설치하지 않고도SafeAreaView와 같은 컴포넌트나useSafeAreaInsets()등의 함수를 사용할 수 있다.
좌측, 혹은 우측에 drawer(sidebar)를 만들 수 있도록 도와준다.
설치
사용자의 제스쳐를 인식하기 위한 react-native-gesture-handler와 더욱 개선된 성능으로 애니메이션을 제공하는 react-native-reanimated를 함께 설치해준다.
$ yarn add @react-navigation/drawer react-native-gesture-handler react-native-reanimated
사용법
먼저 @react-navigation/drawer 라이브러리에서 제공하는 createDrawerNavigator() 함수를 사용하여 Drawer 객체를 만들어 준다.
import {createDrawerNavigator} from "@react-navigation/drawer";
const Drawer = createDrawerNavigator();
function App() {
return (
// ...
)
}
그리고 Stack Navigator와 마찬가지로 Drawer 객체의 Navigator, Screen 컴포넌트를 활용하여 스크린을 구성한다.
// ...
function App() {
return (
<NavigationContainer>
<Drawer.Navigator
initialRouteName="Home"
drawerPosition="left"
backBehavior="history">
<Drawer.Screen name="Home" component={HomeScreen} />
<Drawer.Screen name="Setting" component={SettingScreen} />
</Drawer.Navigator>
</NavigationContainer>
);
}
이 때, initialRouteName 속성으로 초기 화면을 설정할 수 있고, drawerPosition으로 drawer가 어느 방향에서 나타날 지 정할 수 있다. backBehavior 속성은 뒤로가기 버튼을 눌렀을 때 나타나는 화면을 결정하는 속성으로, 다음과 같은 값을 지정할 수 있다.
• initialRoute - 가장 첫 번째 화면을 보여준다.
• order - Drawer.Screen 컴포넌트를 사용한 순서에 따라 이전 화면을 보여준다.
• history - 현재 화면을 열기 직전의 화면을 보여준다.
• none - 뒤로가기를 수행하지 않는다.
Drawer의 스타일과 관련한 속성들
• drawerActiveTintColor - 활성화된 항목의 텍스트 색상
• drawerActiveBackgroundColor - 활성화된 항목의 배경색
• drawerInactiveTintColor - 비활성화된 항목의 텍스트 색상
• drawerInactiveBackgroudnColor - 비활성화된 항목의 배경색
• drawerItemStyle - 항목의 스타일
• drawerLabelStyle - 항목 내부의 텍스트 스타일
• drawerContentContainerStyle - 항목들을 감싸고 있는 영역의 스타일
• drawerStyle - 전체 드로어를 감싸고 있는 영역의 스타일
• headerLeft - 헤더 좌측 아이콘 변경
Custom Drawer Component
Drawer.Navigator 컴포넌트의 drawerContent 속성 활용
// ...
function App() {
// ...
return (
<Drawer.Navigator
initialRouteName="Home"
drawerPosition="left"
backBehavior="history"
drawerContent={({navigation}) => (
<SafeAreaView>
<Text>A Custom Drawer</Text>
<Button
onPress={() => navigation.closeDrawer()}
title="Drawer 닫기"
/>
</SafeAreaView>
)}>
// ..
</Drawer.Navigator>
)
}
스크린 하단부의
설치
$ yarn add @react-navigation/bottom-tabs react-native-vector-icons
사용법
@react-navigation/bottom-tabs 라이브러리의 createBottomTabNavigator 함수로 Tab 객체를 만든다. 이후는 앞서 살펴 보았던 내비게이터들과 사용법이 동일하다.
Bottom Tabs에 아이콘 사용
ios
ios/${프로젝트 디렉토리명}/info.plist 파일 최하단에 아래 코드 추가
<key>UIAppFonts</key>
<array>
<string>MaterialIcons.ttf</string>
</array>
android
android/app/build.gradle 파일 최하단에 아래 코드 추가
project.ext.vectoricons = [
iconFontNames: [ 'MaterialIcons.ttf' ]
]
apply from: "../../node_modules/react-native-vector-icons/fonts.gradle"
android/build.gradle이 아닌, android/app/build.gradle 파일임을 주의하자. 에러 잡느라 3시간을 내다 버렸다😢
Bottom Tab의 스타일과 관련한 속성들
• tabBarActiveTintColor - 활성화된 항목의 아이콘과 텍스트 색상
• tabBarActiveBackgroundColor - 활성화된 항목의 배경색
• tabBarInactiveTintColor - 비활성화된 항목의 아이콘과 텍스트 색상
• tabBarInactiveBackgroundColor - 비활성화된 항목의 배경색
• tabBarShowLabel - 항목에서 텍스트의 가시성 설정(기본값: true)
• tabBarShowIcon - 항목에서 아이콘의 가시성 설정(기본값: false)
• tabBarStyle - 하단 탭 스타일
• tabBarLabelStyle - 텍스트 스타일
• tabBarItemStyle - 항목 스타일
• tabBarLabelPosition - 텍스트 위치 beside-icon(아이콘 우측) / below-icon(아이콘 하단)
• tabBarAllowFontScaling - 시스템 폰트 크기에 따라 폰트 크기를 키울지 결정(기본값: true)
• tabBarSafeAreaInset - SafeAreaView의 forceInset 덮어쓰는 객체(기본값: {bottom: 'always', top: 'never'})
• tabBarKeyboardHidesTabBar - 키보드가 나타날 때 하단 탭을 가릴지 결정(기본값: false)
Stack Navigator와 Bottom Tab Navigator 함께 사용하기
Bottom Tab Navigator를 사용하는 MainScreen을 생성한다. 그리고 App 컴포넌트에서 Stack Navigator로 MainScreen을 랜더링하면 Stack Navigator와 Bottom Tab Navigator를 함께 사용할 수 있다.
// MainScreen
function MainScreen() {
return (
<Tab.Navigator
initialRouteName="Home"
screenOptions={{
tabBarActiveTintColor: '#fb8c00',
tabBarShowLabel: false,
}}>
<Tab.Screen
name="Home"
component={HomeScreen}
options={{
title: '홈',
tabBarIcon: ({color, size}) => (
<Icon name="home" color={color} size={size} />
),
}}
/>
// ..
</Tab.Navigator>
);
}
// App
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Main"
component={MainScreen}
options={{headerShown: false}}
/>
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
참고로 Stack Navigator에서 MainScreen을 설정할 때 headerShown을 false로 설정하지 않으면 두 개의 헤더가 나타나므로 이를 false로 설정해주는 것이 중요하다.
설치
$ yarn add @react-navigation/material-top-tabs react-native-tab-view react-native-pager-view
사용법
역시 다른 Navigator들과 마찬가지다. @react-navigation/material-top-tabs 라이브러리의 createMaterialTopTabNavigator 함수로 Tab 객체를 만들어주자.
Material Top Tab의 스타일과 관련한 속성들
• initialRouteName - 초기 화면의 이름
• screenOptions - 화면의 기본 설정. 하단 탭 내비게이션과 비슷한데, 다음과 같은 추가 옵션이 있다.
– swipeEnabled: 화면을 좌우로 스와이프하여 전환(기본값: true).
– lazy: 특정 탭으로 이동해야만 해당 탭을 렌더링(기본값: true).
– lazyPreloadDistance: lazy 속성이 활성화된 상태에서 몇 칸 뒤 화면을 미리 불러올지 설정(기본값: 0).
– lazyPlaceholder: lazy 속성이 활성화되어 있을 때 아직 보이지 않은 화면에서 보여줄 대체 컴포넌트
– tabBarIndicator: 활성화된 탭을 표시하는 컴포넌트
– tabBarIndicatorStyle: 활성화된 탭을 표시하는 컴포넌트의 스타일
• backBehavior - 뒤로가기할 때의 작동 방식
• tabBarPosition - 탭 바의 위치(top 또는 bottom)
• keyboardDismissMode - 키보드를 숨기는 설정
– auto: 기본값. 화면이 바뀔 때 키보드를 숨김
– on-drag: 화면을 드래그할 때 키보드를 숨김
– none: 드래그해도 키보드를 숨기지 않는다
• sceneContainerStyle - 각 화면에 대한 스타일
• style - 전체 탭 뷰에 대한 스타일
• tabBar - 탭 바를 대체할 수 있는 컴포넌트(공식 메뉴얼 참조)
그리고 Tab.Screen의 options Props에는 다음과 같은 값들을 설정할 수 있다.
• tabBarIcon - 아이콘을 보여주는 함수, { focused: boolean, color: string } 타입의 파라미터를 받아온다.
• tabBarLabel - 탭에서 보이는 이름
Material Top Tab Navigator와 비슷하지만, 큰 차이점이 있다. 활성화된 탭에 따라 전체 탭의 배경색을 변경할 수 있다는 점과 배경색을 변경할 때 물결 효과와 함께 자연스러운 색상 변경이 가능하다는 것이다.
설치
$ yarn add @react-navigation/material-bottom-tabs react-native-paper
사용법
@react-navigation/material-bottom-tabs 라이브러리에서 createMaterialBottomTabNavigator 함수를 불러와 Tab 객체를 생성한다.
Tab.Screen 객체의 options 속성에서 tabBarColor라는 속성을 활용하면 특정 탭을 눌렀을 때 전체 탭의 배경색을 지정할 수 있다.
// ...
function App() {
return (
// ...
<Tab.Navigator>
<Tab.Screen
name="Search"
component={SearchScreen}
options={{
tabBarLabel: '검색',
tabBarIcon: ({color}) => (
<Icon name="search" color={color} size={24} />
),
// Search 탭을 클릭했을 때, 내비게이터 배경색 을 "gray"로 변경
tabBarColor: 'gray',
}}
/>
</Tab.Navigator>
)
}
Material Bottom Tab의 스타일과 관련한 속성들
• initialRouteName - 초기 화면의 이름
• screenOptions - 화면의 기본 설정
• backBehavior - 뒤로가기할 때의 작동 방식
• shifting - 해당 값이 true로 지정되어 있으면 탭이 변경될 때마다 배경색을 변경하고, 활성화된 탭만 탭의 이름을 보여준다. 탭의 개수가 세 개 이상이면 기본적으로 true로 설정된다. 만약 이 값을 false로 지정하면 탭마다 배경색을 따로 따로 지정할 수 없고, 모든 탭에 이름이 보이게 된다.
• labeled - 탭 아이콘 하단에 탭의 이름을 나타낼지 결정한다. 이 값을 false로 지정하면 모든 탭에 이름이 나타나지 않는다.(기본값: true).
• activeColor - 활성화된 탭의 아이콘과 텍스트의 색상
• inactiveColor - 비활성화된 탭의 아이콘과 텍스트의 색상
• barStyle - 탭 바에 스타일 적용
Tab.Screen의 options
• tabBarIcon - 아이콘을 보여주는 함수, { focused: boolean, color: string } 타입의 파라미터를 받아온다.
• tabBarLabel - 탭에 보이는 이름
• tabBarBadge: 탭 아이콘에 배지를 보여준다. 이 값을 true로 설정하면 아이콘 우측 상단에 빨간색 점을 보여주고, 이 값을 문자열 또는 숫자로 입력하면 그 내용이 배지에 나타난다.
Material Bottom Tab Navigator와 헤더 타이틀 동기화하기
Material Bottom Tab Navigator에는 header가 없기 때문에 별도의 작업이 없으면 Stack Navigator에서 설정된 title이 헤더의 제목으로 보여진다. 그래서 내비게이터에서 선택된 탭과 헤더의 제목을 동기화 하는 과정이 필요하다.
import {
getFocusedRouteNameFromRoute,
NavigationContainer,
} from '@react-navigation/native';
function getHeaderTitle(route) {
const routeName = getFocusedRouteNameFromRoute(route);
return routeName;
}
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen
name="Main"
component={MainScreen}
options={({route}) => ({
// title 지정
title: getHeaderTitle(route),
})}
/>
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
@react-navigation/native에서 제공하는 getFocusedRouteNameFromRoute 함수는 route 객체를 인자로 받아 현재 열려 있는 화면의 제목을 조회한다.
해당 함수는 화면이 바뀌기 전, 즉 가장 초기 화면에서는 undefined을 반환하기 때문에 값이 undefined라면 기본 화면의 이름을 사용하도록 구현해주어야 한다.
// ..
function getHeaderTitle(route) {
// getFocusedRouteNameFromRoute(route)의 반환값이 없다면 "Home"
const routeName = getFocusedRouteNameFromRoute(route) ?? 'Home';
const nameMap = {
Home: '홈',
Search: '검색',
Notification: '알림',
Message: '메시지',
};
return nameMap[routeName];
// ..
useNavigation()
스크린으로 사용되고 있지 않은 컴포넌트에서도 navigation 객체를 사용할 수 있다.
import {useNavigation} from '@react-navigation/native';
//..
function OpenDetail() {
const navigation = useNavigation()
return (
<Button
title="Detail 1 열기"
onPress={() => navigation.push("Detail", {id: 1})}
/>
)
}
useRoute()
앞서 살펴본 useNavigation()과 비슷하게 스크린이 아닌 컴포넌트에서 route 객체를 사용할 수 있도록 해준다.
useFocusEffect()
화면을 열었다가 돌아왔을 때 특정 작업을 하고 싶을 때 사용하는 hook. useFocusEffect()는 항상 useCallBack()과 함께 사용해야 한다. 그렇지 않으면 컴포넌트가 리랜더링 될 때마다 useFocusEffect()에 등록된 함수가 호출되어 성능이 저하될 것이다.
useEffect() vs useFocusEffect()
Navigator를 통해Home화면에서Detail화면으로 넘어갈 때,HomeScreen은 사라지는 것이 아니라 그대로 남아 있고, 그 위에DetailScreen이 겹쳐져서 나타난다. 그래서DetailScreen에서 뒤로가기를 눌러도HomeScreen은 새로 랜더링 되지 않고, 그저 가림막이 없어져 다시 화면에 보여지는 것이기 때문에useEffect()가 작동하지 않는다.
useFocusEffect()는 화면에 돌아왔을 때 항상 실행하는 것이기 때문에 현재 화면에서 다른 화면으로 넘어갈 때 특정 작업을 실행하고자 한다면 꼭useFocusEffect()를 사용해주자.