본문 바로가기

React-Native

[RN] Navigator 구조 설계

RN 프로젝트를 진행하며 현재 앱 기획에 맞게 Navigator, Screen 구조를 어떤식으로 잡으면 좋을지 고민해보았다.

우선 유저이탈률을 줄이기 위해 로그인은 앱 진입 시점이 아닌, 로그인이 필요할 때 로그인 스크린이 Stack에 쌓인다.

 

또한 앱 진입 시 메인에는 TabNavigator가 존재하고 하단에는 여러개의 Tab이 존재하는 구조였다.


export type AuthStackParamList = {
  Login: { redirectTo?: string } | undefined;
  Signup: undefined;
};

export type RootStackParamList = {
  Main: undefined;
  Auth: { redirectTo?: string } | undefined; // 로그인 후 돌아갈 곳 (옵션)
};

export type EventStackParamList = {
  EventDetail: { eventId: string };
  EventApply: undefined;
  ApplicantInfo: undefined;
  Ticket: undefined;
  AddPhotoTicket: undefined;
};

export type ProfileStackParamList = {
  ProfileEdit: undefined;
  ProfileSetting: undefined;
};

export type ChatStackParamList = {
  ChatDetail: { chatId: string };
};

export type TabStackParamList = {
  Chat: undefined;
  Event: undefined;
  Profile: undefined;
};


우선 네비게이션 타입을 위처럼 잡았다. 

import { NavigationContainer } from "@react-navigation/native";
import { createNativeStackNavigator } from "@react-navigation/native-stack";
import { RootStackParamList } from "app/types/navigation";
import AuthNavigator from "./AuthNavigator";
import MainNavigator from "./MainNavigator";

type Props = {};

const RootStack = createNativeStackNavigator<RootStackParamList>();

export default function RootNavigator({}: Props) {
  return (
    <NavigationContainer>
      <RootStack.Navigator screenOptions={{ headerShown: false }}>
        {/* 앱 진입 시점: 항상 Main부터 */}
        <RootStack.Screen name="Main" component={MainNavigator} />

        {/* 필요할 때만 navigate 해서 여는 로그인 플로우 */}
        <RootStack.Screen
          name="Auth"
          component={AuthNavigator}
          options={{
            gestureEnabled: true,
            fullScreenGestureEnabled: true,
            animation: "slide_from_right",
          }}
        />
      </RootStack.Navigator>
    </NavigationContainer>
  );
}


RootNavigator에서 MainNavigator를 렌더하고 MainNavigator안에서 navigationGuard를 만들어 특정 상황에서 인증이 필요하다면 AuthNavigator로 이동시켜주었다.

AuthNavigator안에서 로그인, 회원가입, 소셜로그인 등의 처리가 이루어졌다.

import { createNativeStackNavigator } from "@react-navigation/native-stack";
import ChatDetailScreen from "../screens/Chat/ChatDetailScreen";
import EventDetailScreen from "../screens/Event/EventDetailScreen";
import ProfileEditScreen from "../screens/Profile/ProfileEditScreen";
import TabNavigator from "./TabNavigator";

import AddPhotoTicketScreen from "app/screens/Event/AddPhotoTicketScreen";
import ApplicantInfoScreen from "app/screens/Event/ApplicantInfoScreen";
import EventApplyScreen from "app/screens/Event/EventApplyScreen";
import TicketScreen from "app/screens/Event/TicketScreen";
import ProfileSettingScreen from "app/screens/Profile/ProfileSettingScreen";
import {
  ChatStackParamList,
  EventStackParamList,
  ProfileStackParamList,
} from "app/types/navigation";

type MainStackParamList = EventStackParamList &
  ProfileStackParamList &
  ChatStackParamList & {
    MainTabs: undefined;
  };

const Stack = createNativeStackNavigator<MainStackParamList>();

export default function MainNavigator() {
  return (
    <Stack.Navigator
      screenOptions={{
        headerShown: false,
        gestureEnabled: true,
        fullScreenGestureEnabled: true,
        animation: "slide_from_right",
      }}
      initialRouteName="MainTabs"
    >
      {/* 1) 앱 진입 후 첫 화면 → TabNavigator */}
      <Stack.Screen name="MainTabs" component={TabNavigator} />

      {/* 2) 탭 아래 상세 화면들 (탭 안 보이는 스크린들) */}

      {/* chat */}
      <Stack.Screen name="ChatDetail" component={ChatDetailScreen} />

      {/* event */}
      <Stack.Screen name="EventDetail" component={EventDetailScreen} />
      <Stack.Screen name="EventApply" component={EventApplyScreen} />
      <Stack.Screen name="ApplicantInfo" component={ApplicantInfoScreen} />
      <Stack.Screen
        name="Ticket"
        component={TicketScreen}
        options={{
          gestureEnabled: false,
          fullScreenGestureEnabled: false,
        }}
      />
      <Stack.Screen name="AddPhotoTicket" component={AddPhotoTicketScreen} />
      {/* profile */}
      <Stack.Screen name="ProfileSetting" component={ProfileSettingScreen} />
      <Stack.Screen name="ProfileEdit" component={ProfileEditScreen} />
    </Stack.Navigator>
  );
}


MainNavigator에서는 위처럼 TabNavigator를 init으로 렌더한다. TabNavigator에서 각 탭별로 스크린을 연결하고 initialScreen을 주어 진입지점을 처리할 수 있었다. 그 이후 추가로 스크린이 열려야 하는 경우를 카테고리별로 나눠서 지정해주었다.


page라우팅에 익숙해져 개념이 생소하긴 했지만 보다보니 금방 적응이 되어 다행이다,,

앱이 고도화되면 추가로 Navigator를 의도에 맞게 생성해나가는 방식으로 접근해야 할 것 같다.