처음 배우는 리액트 네이티브

8장 내비게이션

youbing 2025. 1. 28. 19:22
  • 리액트 네이티브에서는 내비게이션 기능을 지원하지 않아, 외부 라이브러리를 이용 -> 리액트 내비게이션n(React Navigation) 라이브러리
  • 화면 간의 전환을 관리하는 등에 사용.

 

8.1 리액트 네비게이션

기본 세팅

npx create-expo-app my-app
cd navigation
npm install @react-navigation/native
npm install styled-components
  • 리액트 내비게이션 라이브러리가 지원하는 내비게이션 종류 : 스택(stack) 내비게이션, 탭(tab) 내비게이션, 드로어(drawer) 내비게이션

 

내비게이션 구조

내비게이션 구조

  • Screen 컴포넌트 : 화면으로 사용되는 컴포넌트
    • 필요한 속성 : name(화면 이름), component(화면으로 사용될 컴포넌트)
    • 화면으로 사용되는 컴포넌트에는 항상 navigationroute가 props로 전달됨.
  • Navigator 컴포넌트 : 화면을 관리하는 중간 관리자 역할, 내비게이션을 구성하며 여러 개의 Screen 컴포넌트를 자식 컴포넌트로 갖고 있음.
  • NavigationContainer 컴포넌트 : 내비게이션의 계층 구조와 상태를 관리하는 컨테이너 역할, 최상위 컴포넌트

 

설정 우선 순위

설정 우선 순위

  • Navigator 컴포넌트의 속성으로 수정 -> 자식 컴포넌트로 존재하는 모든 컴포넌트에 적용됨.
  • Screen 컴포넌트의 속성으로 수정
  • 화면으로 사용되는 컴포넌트의 props로 전달되는 navigation을 이용해서 수정
    • 작은 범위일수록 우선순위가 높음.

 

폴더 구조와 라이브러리 설치

폴더 구조

# 대부분의 내비게이션에서 반드시 필요한 종속성 설치
npx expo install react-native-gesture-handler
npx expo install react-native-reanimated
npx expo install react-native-screens
npx expo install react-native-safe-area-context
npx expo install @react-native-community/masked-view

8.2 스택 네비게이션

npm install @react-navigation/stack
  • 일반적으로 가장 많이 사용되는 내비게이션
  • 현재 화면 위에 다른 화면을 쌓으면서 화면 이동 (push / pop)

 

// app/navigations/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 initialRouteName="Home">
      <Stack.Screen name="Home" component={Home} />
      <Stack.Screen name="List" component={List} />
      <Stack.Screen name="Item" component={Item} />
    </Stack.Navigator>
  );
};

export default StackNavigation;
// app/App.js
import React from "react";
// import { NavigationContainer } from "@react-navigation/native";
import StackNavigation from "./navigations/Stack";

const App = () => {
  return <StackNavigation />;
};

export default App;
  • createStackNavigator() : 스택 내비게이션 생성 -> 화면을 구성하는 Screen 컴포넌트, Screen 컴포넌트를 관리하는 Navigator 컴포넌트
    • initialRouteName : 가장 먼저 보일 컴포넌트를 명시적으로 표시 (default : 가장 위에 있는 컴포넌트가 제일 먼저 보임.)
  • Expo SDK를 사용하는 경우 Expo SDK 내에서 기본 내비게이션 환경이 자동으로 설정되므로, NavigationContainer를 명시적으로 추가하지 않아도 됨. 하지만, 이는 Expo의 관리되는 환경에서만 가능함.

 

화면 이동

// app/screens/List.js
const List = ({ navigation }) => {
  const onPress = (item) =>
    navigation.navigate("Item", { id: item.id, name: item.name });

  return (
    <Container>
      <StyledText>List</StyledText>
      {items.map((item) => (
        <Button key={item.id} title={item.name} onPress={() => onPress(item)} />
      ))}
    </Container>
  );
};

// app/screens/Item.js
const Item = ({ route }) => {
  return (
    <Container>
      <StyledText>Item</StyledText>
      <StyledText>ID: {route.params.id}</StyledText>
      <StyledText>Name: {route.params.name}</StyledText>
    </Container>
  );
};
  • Screen 컴포넌트의 components로 지정된 컴포넌트는 화면으로 이용되고 navigation이 props로 전달됨.
    • navigation.navigate('Screen 컴포넌트의 name')
    • 2번째 파라미터에 객체 형식으로 데이터를 전달할 수 있음. -> 컴포넌트의 props로 전달되는 route.params를 통해 받을 수 있음.

 

화면 배경색 수정하기

문제 : 내비게이션은 화면 전체를 차지하고 있지만 화면으로 사용된 컴포넌트의 영역이 전체를 차지하고 있지 않아 뒤의 배경이 보이는 경우

  1. 스타일에 flex: 1을 설정
  2. Navigator 컴포넌트의 screenOptions 속성을 활용하여 스택 내비게이션의 화면 배경색 수정
const StackNavigation = () => {
  return (
    <Stack.Navigator
      screenOptions={{ cardStyle: { backgroundColor: "lightblue" } }}
    >
      <Stack.Screen name="Home" component={Home} />
      ...
    </Stack.Navigator>
  );
};
  • 화면의 배경색은 일반적으로 동일하게 사용하므로, Screen 컴포넌트보다 Navigator 컴포넌트의 screenOptions에 설정해서 화면 전체에 적용되도록 하는 것이 편함.
  • 별도로 배경색을 지정하지 않으면 screenOptions에서 지정한 색으로 보임.

 

헤더 수정하기

  • 스택 내비게이션의 헤더(header) : 뒤로 가기 버튼을 제공 / 타이틀(title)을 통해 현재 화면을 알려주는 역할

 

  • 헤더 타이틀 default : Screen 컴포넌트의 name 속성
    • name 자체를 수정해도 괜찮지만, name 속성을 이용한 곳을 모두 찾아서 수정해야 함.
    • Screen 컴포넌트 options 속성에 있는 headerTitle 속성으로 수정 가능.
<Stack.Screen name="Item" component={Item} options={{ headerTitle: "Item Detail" }} />
  • 모든 화면에서 같은 타이틀이 나타나게 하려면 Navigator 컴포넌트의 screenOptions 속성에 headerTitle 지정

 

  • 헤더 스타일 수정 - headerStyle (ex. backgroundColor 등)
  • 헤더 타이틀 스타일 수정 - headerTitleStyle (ex. fontSize 등)
<Stack.Navigator
  screenOptions={{
    cardStyle: { backgroundColor: "lightblue" },
    headerStyle: {
      height: 110,
      backgroundColor: "#95a5a6",
      borderBottomWidth: 5,
      borderBottomColor: "#34495e",
    },
    headerTitleStyle: { color: "white", fontSize: 24 },
  }}
>
  • 타이틀 정렬(headerTitleAlign)은 현재 두 플랫폼 모두 center가 default로 바뀜.

 

  • 타이틀 컴포넌트 변경 : 타이틀에 문자열이 아닌 것을 렌더링하고 싶을 때
    -> headerTitle에 컴포넌트를 반환하는 함수를 지정
    • 함수가 설정되면 해당 함수의 파라미터로 styletintColor 등이 포함된 객체가 전달됨.
    • style == headerTitleStyle / tintColor == headerTintColor
import { MaterialCommunityIcons } from "@expo/vector-icons";
...
<Stack.Navigator
  screenOptions={{
    headerStyle: { ... },
    headerTitleStyle: { color: "white", fontSize: 24 },
    headerTitle: ({ style }) => (
      <MaterialCommunityIcons name="react" style={style} />
    ),
  }}
>
Note vector-icons : Expo 프로젝트에서 기본적으로 설치되는 라이브러리

 

  • 버튼 수정하기 (헤더 왼쪽에 있는 뒤로 가기 버튼)
    • headerBackTitle : 버튼 타이틀 텍스트 변경
      • 버튼 타이틀에 빈 값을 주고 싶으면 "" 혹은 null을 넣는다.
<Stack.Screen
  ...
  options={{ headerBackTitle: "Prev" }}
/>

 

  • 버튼 스타일 수정하기
    • headerBackTitleStyle : 버튼 타이틀 글자 색/크기 등의 스타일 지정
    • headerTintColor : 헤더에 있는 요소 전체의 색을 통일 (headerTitleStyle / headerBackTitleStyle이 더 우선순위가 높음.)
<Stack.Screen
  ...
  options={{
    headerTitleStyle: { fontSize: 24 },
    ...
  }}
/>

 

  • 버튼 컴포넌트 변경
    • headerBackImage : 뒤로 가기 버튼 아이콘 변경 (컴포넌트를 반환하는 함수를 전달해서 두 플랫폼이 동일한 이미지를 렌더링하게 함.)
headerBackImage: ({ tintColor }) => {
  const style = {
    marginRight: 5,
    marginLeft: Platform.OS === "ios" ? 11 : 0,
  };
  return (
    <MaterialCommunityIcons
      name="keyboard-backspace"
      size={30}
      color={tintColor}
      style={style}
    />
  );
},

 

  • headerLeft[headerRight] : 뒤로 가기 버튼의 이미지가 아니라 버튼 전체를 변경 (컴포넌트를 반환하는 함수 전달)
// app/screens/Item.js
const Item = ({ navigation, route }) => {
  useLayoutEffect(() => {
    navigation.setOptions({
      headerBackTitleVisible: false,
      headerTintColor: white,
      headerLeft: ({ onPress, tintColor }) => {
        return (
          <MaterialCommunityIcons
            name="keyboard-backspace"
            size={30}
            style={{ marginLeft: 11 }}
            color={tintColor}
            onPress={onPress}
          />
        );
      },
      headerRight: ({ tintColor }) => (
        <MaterialCommunityIcons
          name="home-variant"
          size={30}
          style={{ marginRight: 11 }}
          color={tintColor}
          onPress={() => navigation.popToTop()}
        />
      ),
    });
  }, []);

  return ( ... );
};
  • headerLeft 함수의 파라미터 중 onPress에는 뒤로 가기 버튼 기능이 전달됨. (뒤로 가기 버튼의 기능을 그대로 기용하고 싶을 때 좋음.)
  • headerRight 함수의 파라미터에는 tintColor만 전달되므로 onPress에 원하는 행동을 정의해야 함.
  • navigation.popToTop() : 현재 쌓여 있는 모든 화면을 내보내고 첫 화면으로 돌아감.

 

Note. useLayoutEffect VS useEffect

  useLayoutEffect useEffect
실행 시점 렌더링 직후, 화면에 반영되기 전 렌더링 후, 화면에 반영된 후
동기 / 비동기 동기 비동기
UI 변경 영향 여부 레이아웃 측정, DOM 수정 등 비동기 작업, API 호출 등
주요 사용처 UI 변경 전에 실행되므로 UI에 영향을 미칠 수 있음. UI가 반영된 후에 실행되어 UI에 영향 없음.

 

  • 헤더 감추기
    • headerMode : Navigator 컴포넌트의 속성
      • float : 헤더가 상단에 유지, 하나의 헤더 사용 (iOS)
      • screen : 각 화면마다 헤더를 가짐, 화면 변경과 함께 나타나거나 사라짐. (안드로이드)
      • none : 헤더 X
    • headerShown : 헤더 렌더링 여부 (default : true)

8.3 탭 내비게이션

npm install @react-navigation/bottom-tabs

화면 구성

// app/navigation/Tab.js
import React from "react";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { Mail, Meet, Settings } from "../screens/TabScreens";

const Tab = createBottomTabNavigator();

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

export default TabNavigation;
// app/App.js
import React from "react";
// import { NavigationContainer } from "@react-navigation/native";
import TabNavigation from "./navigations/Tab";

const App = () => {
  return <TabNavigation />;
};

export default App;
  • createBottomTabnavigator() : 탭 내비게이션 생성
  • 탭 바에 있는 버튼 순서는 Navigator 컴포넌트의 자식인 Screen 컴포넌트의 순서와 동일
    • initialRouteName : 탭 버튼의 순서는 변경하지 않고 렌더링되는 첫 번째 화면 변경 (default : 첫 번째 Screen 컴포넌트)
  • Expo SDK를 사용하는 경우 Expo SDK 내에서 기본 내비게이션 환경이 자동으로 설정되므로, NavigationContainer를 명시적으로 추가하지 않아도 됨. 하지만, 이는 Expo의 관리되는 환경에서만 가능함.

 

탭 바 수정하기

  • tabBarIcon : 버튼 아이콘 설정 (default : 비어 있음)
    • 컴포넌트를 반환하는 함수를 지정하면 아이콘 자리에 컴포넌트가 렌더링됨.
    • 파라미터로 color, size, focused 값을 포함한 객체 전달
const TabNavigation = () => {
  const TabIcon = ({ name, size, color }) => (
    <MaterialCommunityIcons name={name} size={size} color={color} />
  );

  return (
    <Tab.Navigator>
      <Tab.Screen
        name="Mail"
        component={Mail}
        options={{
          tabBarIcon: (props) => TabIcon({ ...props, name: "email" }),
        }}
      />
      ...
    </Tab.Navigator>
  );
};
  • Screen 컴포넌트마다 탭 버튼 아이콘을 지정하지 않고 한곳에서 모든 버튼의 아이콘을 관리하려면, Navigator 컴포넌트의 screenOptions 속성 사용
const TabNavigation = () => {
  const TabIcon = ({ name, size, color }) => (
    <MaterialCommunityIcons name={name} size={size} color={color} />
  );

  return (
    <Tab.Navigator
      screenOptions={({ route }) => ({
        tabBarIcon: (props) => {
          let name = "";
          if (route.name === "Mail") name = "email";
          else if (route.name === "Meet") name = "video";
          else name = "cog";
          return TabIcon({ ...props, name });
        },
      })}
    >
      ...
    </Tab.Navigator>
  );
};

 

  • 라벨 수정하기
    • tabBarLabel : 탭 버튼 아이콘 아래의 라벨(label) 변경 (default : Screen 컴포넌트의 name)
    • tabBarLabelPosition : 라벨 위치 조정
      • below-icon(default), beside-icon(오른쪽)
    • tabBarLabelStyle : 라벨 렌더링 여부
      • { display: ''none" / "flex" }
<Tab.Navigator
  screenOptions={({ route }) => ({
    tabBarIcon: (props) => { ... },
    tabBarLabelPosition: "beside-icon",
    tabBarLabelStyle: { display: "none" },
  })}
>
  ...
</Tab.Navigator>

 

  • 스타일 수정하기
    • tabBarStyle : 탭 바 스타일 수정
    • tabBar[Inactive]ActiveTintColor : 활성화된 탭의 아이콘/라벨의 색상 설정
<Tab.Navigator
  screenOptions={({ route }) => ({
    ...
    tabBarStyle: { backgroundColor: "#54b7f9" },
    tabBarActiveTintColor: "black",
  })}
>
  ...
</Tab.Navigator>

 

  • 예제 : 활성화 상태에 따라 다른 아이콘이 렌더링되게 변경
<Tab.Navigator screenOptions={{ ... }}>
  <Tab.Screen
    name="Mail"
    component={Mail}
    options={{
      tabBarIcon: (props) =>
        TabIcon({
          ...props,
          name: props.focused ? "email" : "email-outline",
        }),
    }}
  />
  ...
</Tab.Navigator>

 

추가 : 교재에서 쓴 tabBarOptions는 현재 버전에서는 더 이상 사용하지 않으며, 대신 screenOptions로 설정해야 함.
- labelPosition -> tabBarLabelPosition
- showLabel -> tabBarLabelStyle: { display: '~~' }
- style -> tabBarStyle: { backgroundColor: '~~' }
- [in]activeTintColor -> tabBar[InActive]ActiveTintColor

 

 

 

본문에서 설명하는 내용과 사용한 이미지는 책 '처음 배우는 리액트 네이티브'를 참고하였습니다.