Develop/React Native

[React Native] 리액트 네비게이션 라이브러리 사용하기 (stack, tab)

마크투비 2021. 11. 21. 15:37

오늘은 <처음 배우는 리액트 네이티브> 8장 내비게이션에 대해 알아보겠다 😁

Navigation

리액트 네이티브에서는 네비게이션 기능을 지원하지 않아서 외부 라이브러리인 리액트 네비게이션 라이브러리를 이용해야 한다.

1. 리액트 네비게이션

리액트 네비게이션에서 지원하는 네비게이션의 종류는 스택(stack) 네비게이션, 탭(tab) 네비게이션, 드로어(drawer) 네비게이션 세 종류이다. 이 포스트에서는 리액트 네비게이션5 버전을 기준으로 진행할 것이다.

1) 네비게이션의 종류

1️⃣ NavigationContainer 컴포넌트
네비게이션의 계층구조와 상태를 관리하는 컨테이너 역할을 하며, 모든 네비게이션 구성 요소를 감싼 최상위 컴포넌트이다.

2️⃣ Navigator 컴포넌트
화면을 관리하는 중간 관리자 역할로 네비게이션을 구성하며, 여러 개의 Screen 컴포넌트를 자식 컴포넌트로 가진다.

3️⃣ Screen 컴포넌트
Screen 컴포넌트는 화면으로 사용되는 컴포넌트로 namecomponent 속성을 지정해야 한다.

  • name은 화면 이름으로 사용되고 component에는 화면으로 사용될 컴포넌트를 전달한다.
  • 화면으로 사용되는 컴포넌트에는 항상 navigationrouteprop으로 전달된다.

2) 설정 우선 순위

리액트 네비게이션에서 설정할 수 있는 다양한 속성을 수정하는 방법은 다음 세 가지 방법이 있다.
1️⃣ Navigator 컴포넌트의 속성을 수정하는 방법
2️⃣ Screen 컴포넌트의 속성을 수정하는 방법
3️⃣ 화면으로 사용되는 컴포넌트의 props로 전달되는 navigation을 이용해서 수정하는 방법

2️⃣번과 3️⃣번 방법은 해당 화면에만 적용되지만, 1️⃣번 방법은 자식 컴포넌트로 존재하는 모든 컴포넌트에 적용된다.

설정 우선 순위는
1️⃣ Navigator 컴포넌트의 속성을 수정하는 방법
2️⃣ Screen 컴포넌트의 속성을 수정하는 방법
3️⃣ 화면으로 사용되는 컴포넌트의 props로 전달되는 navigation을 이용해서 수정하는 방법
순으로 높아진다. 즉 props로 전달되는 navigation을 이용하는 방법의 우선순위가 가장 높다.

3) 라이브러리 설치

리액트 네비게이션 라이브러리 설치

npm install --save @react-navigation/native
expo install react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view

2. 스택 네비게이션

1) 화면 구성

  • 스택 네비게이션에 필요한 라이브러리 설치하기
    npm install @react-navigation/stack

스택 네비게이션은 가장 많이 사용되는 네비게이션으로, 현재 화면 위에 다른 화면을 쌓으면서 화면을 이동한다. 예를 들면 채팅 애플리케이션에서 채팅방에 입장하는 상황이나 여러 목록 중에서 특정 항목의 상세 화면으로 이동할 때 많이 사용된다.

스택 네비게이션은 화면 위에 새로운 화면을 쌓으면서(push) 이동하기 때문에 이전 화면을 계속 유지한다. 가장 위에 있는 화며을 들어내면(pop) 이전 화면으로 돌아갈 수 있다.

  • createStackNavigator 함수로 스택 네비게이션 생성하기

src/navigation/Stack.js

import React from 'react';
import { createStackNavigator } from '@react-navigation/stack';
import Home from '../screens/Home';
import List from '../screens/List';
import Item from '../screens/Item';

const Stack = createStackNavigator();

const StackNavigation = () => {
  return (
    <Stack.Navigator>
        <Stack.Screen name="Home" component={Home} />
        <Stack.Screen name="List" component={List} />
        <Stack.Screen name="Item" component={Item} />
    </Stack.Navigator>
);
};

export default StackNavigation;

생성된 스택 네비게이션에는 화면을 구성하는 Screen 컴포넌트와 Screen 컴포넌트를 관리하는 Navigator 컴포넌트가 있다.
name에는 화면의 이름을 작성하는데, 이때 Screen 컴포넌트의 name은 반드시 서로 다른 값을 가져야 한다.

2) 화면 이동

Screen 컴포넌트의 component로 지정된 컴포넌트는 화면으로 이용되고, navigationprops로 전달된다.
navigationnavigate 함수는 원하는 화면으로 이동하는 데 사용되는 함수이다.

이번에는 Home 화면에서 props로 전달되는 navigation을 이용해서 버튼을 클릭하면 List 화면으로 이동하도록 만들어보겠다. navigation에 있는 navigate 함수를 이용해서 원하는 화면의 이름을 전달하면 해당 화면으로 이동한다.
✅ 이때 전달되는 화면의 이름은 Screen 컴포넌트의 name 값 중 하나를 입력해야 한다.

src/screens/Home.js

...
const Home = ({ navigation }) => (
  return (
      <Container>
          <StyledText>Home</StyledText>
          <Button
              title="go to the list screen"
              onPress={() => navigation.navigate('List')}
        />
      </Container>
);
};
...

만약 이동하는 화면이 어떤 화면의 상세 화면이라면, 상세 화면은 어떤 내용을 렌더링해야 하는지 전달받아야 한다. navigate 함수를 이용할 때 두 번째 파라미터에 객체를 전달해서 이동하는 화면에 필요한 정보를 함께 전달한다.

navigate 함수를 이용하여 List 화면에서 목록을 클릭하면 해당 항목의 정보와 함께 Item 화면으로 이동하도록 해보겠다. Item 화면으로 이동할 때 항목의 idname을 함께 전달한다. 전달된 내용은 컴포넌트의 props로 전달되는 routeparams를 통해 확인할 수 있다.

src/screens/List.js

const List = ({ navigation }) => {
  const _onPress = item => {
    navigation.navigate('Item', {id: item._id, name: item.name });
  };
  ...
  );
  ...

이번에는 Item 화면에서 전달되는 params를 이용하여 화면에 항목의 idname을 출력해보겠다.

src/screens/Item.js

const Item = ({ route }) => {
  return (
    <Container>
        <StyledText>Item</StyledText>
        <StyledText>ID: {route.params.id}</StyledText>
        <StyledText>Name: {route.params.name}</StyledText>
    </Container>    
);
};
...

3. 화면 배경색 수정

src/screens/Home.js

...
const Container = styled.View`
    background-color: #ffffff;
    align-items: center;
`;
...

cardStyle을 이용해서 스택 네비게이션의 화면 배경색을 지정한다. 화면의 배경색은 일반적으로 동일하게 사용하므로, 화면마다 설정하기보다 Navigator 컴포넌트의 screenOptions에 설정해서 전체에 적용하도록 한다.

src/navigations/Stack.js

const StackNavigation = () => {
  return (
    <Stack.Navigator
        initialRouteName="Home"
        screenOptions={{ cardStyle: {backgroundColor: '#ffffff'} }}
      >
      ...
    </Stack.Navigator>
);

4. 헤더 수정하기

스택 네비게이션의 헤더(header)는 뒤로 가기 버튼이나 타이틀(title)을 통해 현재 화면을 알려주는 역할을 한다.

타이틀 수정하기

1️⃣ name의 속성 값 변경하기
헤더의 타이틀은 Screen 컴포넌트의 name 속성을 기본값으로 사용한다. name을 원하는 값으로 수정해서 헤더의 타이틀을 수정한다. Item 화면을 나타내는 Screen 컴포넌트의 name 속성을 Detail로 변경했다.

src/navigations/Stack.js

const StackNavigation = () => {
  return (
    <Stack.Navigator
        initialRouteName="Home"
        screenOptions={{ cardStyle: {backgroundColor: '#ffffff'} }}
      >
        <Stack.Screen name="Detail" component={Item} />
    </Stack.Navigator>
);
};

name 속성을 Detail로 변경함에 따라 원래 Item 화면으로 이동할 때 navigate 함수에 전달하는 첫 번째 파라미터 값도 변경되어야 한다. 이렇게 모든 name 속성을 이용한 곳을 수정해야 한다는 단점이 있다.

2️⃣ headerTitle 속성 이용하기
Screen 컴포넌트의 options를 이용해서 개별 화면 설정을 수정할 수 있다. headerTitle 속성을 이용해서 List 화면의 타이틀을 List Screen으로 변경했다.

src/navigations/Stack.js

const StackNavigation = () => {
  return (
    <Stack.Navigator
        initialRouteName="Home"
        screenOptions={{ cardStyle: {backgroundColor: '#ffffff'} }}
      >
        <Stack.Screen 
            name="List" 
            component={List} 
            options={{ headerTitle: 'List Screen' }}
        />
    </Stack.Navigator>
);
};

모든 화면에서 같은 타이틀이 나타나도록 수정하는 방법은 Navigator 컴포넌트의 screen Options 속성에 headerTitle을 지정하는 방법이 있다.

스타일 수정하기

헤더의 배경색 등을 수정하는 headStyle과 헤더의 타이틀 컴포넌트의 스타일을 수정하는 headerTitleStyle 속성을 이용해서 헤더의 스타일을 수정할 수 있다.

headerStyle을 이용해서 헤더의 스타일을 변경하고, headerTitleStyle 속성을 이용해서 타이틀의 스타일을 변경해보겠다. 그리고 안드로이드와 iOS에서 모두 동일하게 타이틀 정렬을 하기 위해 headerTitleAlign 속성을 이용한다. 이때 headerTitleAlign 속성의 값은 leftcenter 둘 중에 하나를 선택할 수 있다.

src/navigations/Stack.js

const StackNavigation = () => {
  return (
    <Stack.Navigator
        initialRouteName="Home"
        screenOptions={{ 
            cardStyle: {backgroundColor: '#ffffff'},
              headerStyle: {
                height: 110,
                backgroundColor: '#95a5a6',
                borderBottomWidth: 5,
                borderBottomColor: '#34495e',
            },
            headerTitleStyle: {color: '#ffffff', fontSize: 24},
            headerTitleAlign: 'center', 
          }}
      >
      ...
    </Stack.Navigator>
);
};

타이틀 컴포넌트 변경

타이틀에 문자열이 아닌 다른 것을 렌더링 하는 방법은 headerTitle 속성에 컴포넌트를 반환하는 함수를 지정하면 된다. headerTitle에 함수가 설정되면 해당 함수의 파라미터로 styletintColor 등이 포함된 객체가 전달된다.
styleheaderTitleStyle에 설정된 값이고, tintColorheaderTintColor에 지정된 값이 전달된다.

다음 코드는 vector-icons에서 제공하는 컴포넌트를 이용해서 리액트 로고가 렌더링 되도록 작성한 것이다.

src/navigations/Stack.js

...
import { MaterialCommunityIcons } from '@expo/vector-icons';

...
const StackNavigation = () => {
  return (
    <Stack.Navigator
        initialRouteName="Home"
        screenOptions={{ 
            cardStyle: {backgroundColor: '#ffffff'},
              headerStyle: {
                height: 110,
                backgroundColor: '#95a5a6',
                borderBottomWidth: 5,
                borderBottomColor: '#34495e',
            },
            headerTitleStyle: {color: '#ffffff', fontSize: 24},
            headerTitleAlign: 'center', 
            headerTitle: ({ style }) => (
              <MaterialCommunityIcons name="react" style={style}/>
            ),
          }}
      >
      ...
    </Stack.Navigator>
);
};

버튼 수정하기

헤더 왼쪽에 뒤로가기 버튼을 만들어보겠다. 안드로이드에서 뒤로가기 버튼은 버튼의 타이틀을 보여주지 않지만 iOS에서는 이전 화면의 타이틀을 버튼의 타이틀로 보여준다. headerBackTitleVisible을 이용하면 두 플랫폼의 버튼 타이틀 렌더링 여부를 동일하게 설정할 수 있다. 이전 화면의 이름이 아닌 다른 값을 이용하고 싶은 경우 headerBackTitle을 이용한다.

버튼 타이틀의 스타일은 headerBackTitleStyle을 이용해서 글자의 색, 글자 크기 등 다양한 스타일을 지정할 수 있다.
headerTintColor에 지정된 색은 버튼뿐만 아니라 헤더의 타이틀에도 적용된다.
이때 우선순위는 headerBackTitleStyleheaderTitleStyleheaderTintColor보다 높다.

src/navigations/Stack.js

...
import { MaterialCommunityIcons } from '@expo/vector-icons';

...
const StackNavigation = () => {
  return (
    <Stack.Navigator
         ...
      >
    <Stack.Screen name="Home" component={Home} />
    <Stack.Screen
        name="List"
        component={List}
        options={{
             headerTitle: 'List Screen',
             headerBackTitleVisible: true,
             headerBackTitle: 'Prev',
             headerTitleStyle: {fontSize: 24},
               headerTintColor: '#e74c3c',
        }}
        />
    </Stack.Navigator>
);
};

3. 탭 네비게이션

탭 네비게이션은 화면 위나 아래에 위치하며, 탭 버튼을 누르면 버튼과 연결된 화면으로 이동한다. 주로 채팅 어플리케이션에서 쉽게 확인할 수 있다.

1. 라이브러리 설치

npm install @react-navigation/bottom-tabs

2. 화면 구성

3개의 버튼과 해당 버튼에 연결된 화면으로 구성된 탭 네비게이션을 만드는 코드는 다음과 같다.
현재 화면을 확일할 수 있는 텍스트가 나타나는 간단한 컴포넌트 3개를 만들었다.

src/screens/TabScreens.js

import React from 'react';
import styled from 'styled-components/native';

const Container = style.View`
    flex: 1;
    justify-content: center;
    align-items: center;
`;
const StyledText = styled.Text`
    font-size: 30px;
`;

export const Mail = () => {
  return (
    <Container>
        <StyledText>Mail</StyledText>
    </Container>
);
};

export const Meet = () => {
  return (
    <Container>
        <StyledText>Meet</StyledText>
    </Container>
);
};

export const Settings = () => {
  return (
    <Container>
        <StyledText>Settings</StyledText>
    </Container>
);
};

위에서 만든 컴포넌트를 이용해서 탭 네비게이션을 만들어보겠다. crateBottomTabNavigatior 함수를 이용해 탭 네비게이션을 생성한다. 탭 네비게이션에도 스택 네비게이션과 동일하게 Navigator 컴포넌트, Screen 컴포넌트가 있다. 다음 코드를 작성하고 App 컴포넌트에서 탭 네비게이션을 사용하면 하단에 3개의 버튼이 놓인 탭 바가 있고, 탭의 버튼을 클릭할 때마다 화면이 변경되는 것을 확인할 수 있다.

첫번째로 렌더링되는 화면을 변경하고 싶은 경우 initialRouteName 속성을 이용한다.

src/screens/Tab.js

import React from 'react';
import { createBottomTabNavigator } from '...';
import { Mail, Meet, Settings } from '../screens/TabScreens';

const Tab = createBottomTabNavigator();

const TabNavigation = () => {
  return (
    <Tab.Navigator initialRouteName="Settings">
        <Tab.Screen name="Mail" component={Mail} />
        <Tab.Screen name="Meet" component={Meet} />
        <Tab.Screen name="Settings" component={Settings} />
    </Tab.Navigator>
);
};

export default TabNavigation;

3. 탭 바 수정하기

버튼 아이콘 설정하기

탭 네비게이션의 기본 설정은 탭 버튼 아이콘이 지정되어 있지 않다. tabBarIcon을 이용해서 탭 버튼에 아이콘을 렌더링할 수 있다. tabBarIcon에 컴포넌트를 반환하는 함수를 지정하면 버튼의 아이콘이 들어갈 자리에 해당 컴포넌트를 레더링한다. tabBarIcon에 설정된 함수에는 color, size, focused 값을 포함한 객체가 파라미터로 전달된다.

src/navigatoins/Tab.js

...
import { MaterialCommunityIcons } from '@expo/vector-icons';

const TabIcon = ({ name, size, color }) => {
  return <MaterialCommunityIcons name={name} size={size} color={color} />;
};

const Tab = createBottomTabNavigator();

const TabNavigation = () => {
  return (
    <Tab.Navigator initialRouteName="Settings">
        <Tab.Screen 
            name="Mail" 
            component={Mail} 
            options={{
                 tabBarIcon: props => TabIcon({...props, name: 'email' })
        />
        <Tab.Screen 
            name="Meet" 
            component={Meet} 
            options={{
                 tabBarIcon: props => TabIcon({...props, name: 'video' })
        />
        <Tab.Screen 
            name="Settings" 
            component={Settings} 
            options={{
                 tabBarIcon: props => TabIcon({...props, name: 'settings' })
        />
    </Tab.Navigator>
);
};

만약 Screen 컴포넌트마다 탭 버튼 아이콘을 지정하지 않고 한곳에서 모든 버튼의 아이콘을 관리하고 싶은 경우 Navigaotr 컴포넌트의 screenOptions 속성을 사용해서 관리할 수 있다.
screenOptions에 객체를 반환하는 함수를 설정하고 함수로 전달되는 route를 이용한다.

버튼 아이콘 아래에 렌더링되는 버튼의 라벨(label)은 Screen 컴포넌트의 name 값을 기본값으로 사용한다. 탭 버튼의 라벨은 tabBarLabel을 이용해서 변경한다.

src/navigatoins/Tab.js

...
import { MaterialCommunityIcons } from '@expo/vector-icons';

const TabIcon = ({ name, size, color }) => {
  return <MaterialCommunityIcons name={name} size={size} color={color} />;
};

const Tab = createBottomTabNavigator();

const TabNavigation = () => {
  return (
    <Tab.Navigator 
        initialRouteName="Settings"
        screenOptions={({ route }) => ({
            tabBarIcon: props => {
              let name='';
              if (route.name === 'Mail') name = 'email';
              else if (route.name === 'Meet') name = 'video';
              else name = 'settings';
              return TabIcon({ ...props, name });
            },
  })}
  >
           <Tab.Screen 
            name="Mail" 
            component={Mail} 
            options={{
                 tabBarLabel: 'Inbox',
                 tabBarIcon: props => TabIcon({...props, name: 'email' })
        />
          ...
    </Tab.Navigator>
);
};

스타일 수정하기

탭 네비게이션의 탭 바 배경색은 흰색이 기본값이다.
먼저 화면의 배경색을 다음과 같이 변경한다.

src/screens/TabScreen.js

...
const Container = styled.View`
    flex: 1,
    justify-content: center,
    align-items: center,
    background-color: #54b7f9;
`;
const StyledText = styled.Text`
    font-size: 30px;
    color: #ffffff;
`;
...

탭 바의 스타일은 tabBarOptions 속성에 style 값으로 스타일 객체를 설정해서 변경한다. 탭 버튼의 아이콘은 선택되어 활성화된 상태의 색과 선택되지 않아 비활성 상태의 색을 각각 activeTintColorinactiveTintColor로 설정할 수있다. 만약 라벨이 렌더링되도록 설정되었다면 라벨의 색도 버튼의 활성화 상태에 따라 나타난다.
barTabIcon에 설정한 함수의 파라미터 focused는 버튼의 선택된 상태를 나타내는 값이다. 아래 코드에서는 focused의 값에 따라 버튼이 활성화되었을 때는 내부가 채워진 이미지가 렌더링되고, 비활성화 상태에서는 내부가 비 아이콘이 렌더링된다.

src/navigatoins/Tab.js

...
const TabNavigation = () => {
  return (
    <Tab.Navigator 
        initialRouteName="Settings"
        tabBarOptions={{
            ...
            activeTintColor: '#ffffff',
            inactiveTintColor: '#cfcfcf',
        }}
    >
    <Tab.Screen
        name="Mail"
        component={Mail}
        options={{
            tabBarLabel: 'Inbox',
            tabBarIcon: props =>
                 TabIcon({
                     ...props,
                     name: props.focused ? 'email' : 'email-outline',
                }),
}}
/>
          ...
    </Tab.Navigator>
);
};