Type Checking navigator - React

mementomori·2021년 7월 28일
1

배경 및 필요성

React Navigation 공식 라이브러리는 컴포넌트 간 이동인 라우팅 기능을 제공

  • 각 navigator(stack, bottomTab 등)을 create할 때 타입을 지정해주면, 각 하위의 컴포넌트(스크린)들에 자동으로 내려지는 props(route, navigation)에 대한 타입 체크와 자동 완성이 가능해짐

기본 활용법

https://reactnavigation.org/docs/typescript/

  • stackNavigator을 기준으로 하지만, 다른 navigator도 동일하게 적용가능

1. Param의 구조에 대한 타입 지정

type RootStackParamList = {
  Home: undefined;
  Profile: { userId: string };
  Feed: { sort: 'latest' | 'top' } | undefined;
};

2. navigator 생성 시 1번의 타입을 지정

import { createStackNavigator } from '@react-navigation/stack';
const RootStack = createStackNavigator<RootStackParamList>();

3. 2번에서 생성된 navigator을 사용하여 하위 스크린 구성

<RootStack.Navigator initialRouteName="Home">
  <RootStack.Screen name="Home" component={Home} />
  <RootStack.Screen
    name="Profile"
    component={Profile}
    initialParams={{ userId: user.id }}
  />
  <RootStack.Screen name="Feed" component={Feed} />
</RootStack.Navigator>

개별 스크린에서 타입 체크

개별 스크린에서 타입을 체크하기 위해서는, 각 스크린이 내려받는 props(navigation, route)에 대해 annotate 해야 함

  • 예를 들어, stackNavigation의 개별 스크린에서 navigation prop을 annotate하는 경우 아래와 같음/ 이때 StackNavigationProp이 가지는 parameter는 위에서 생성한 param list object이고, 두번째 parameter는 현재 route 이름임.
import { StackNavigationProp } from '@react-navigation/stack';

type ProfileScreenNavigationProp = StackNavigationProp<
  RootStackParamList,
  'Profile'
>;

type Props = {
  navigation: ProfileScreenNavigationProp;
};

Nesting navigator의 경우

여러 navigator가 중첩되어 있는 경우, screenparams을 중첩되어 있는 스크린에 전달하므로서 스크린들을 navigate 할 수 있음

navigation.navigate('Home', {
  screen: 'Feed',
  params: { sort: 'latest' },
});

1. 중첩된 navigator를 포함하는 스크린으로부터 params를 추출(by NavigatorScreenParams)

import { NavigatorScreenParams } from '@react-navigation/native';

type TabParamList = {
  Home: NavigatorScreenParams<StackParamList>;
  Profile: { userId: string };
};

2. navigation props를 combine (by CompositeNavigationProp)

  • CompositeNavigationProp의 첫번째 인자는 주된 navigation type이고, 두번째 인자는 2순위 navigation type임(부모 navigator) => 스크린 이름은 첫번째 인자에서만 가짐
import { CompositeNavigationProp } from '@react-navigation/native';
import { BottomTabNavigationProp } from '@react-navigation/bottom-tabs';
import { StackNavigationProp } from '@react-navigation/stack';

type ProfileScreenNavigationProp = CompositeNavigationProp<
  BottomTabNavigationProp<TabParamList, 'Profile'>,
  StackNavigationProp<StackParamList>
>;
  • 3개 이상인 경우 아래와 같음
type ProfileScreenNavigationProp = CompositeNavigationProp<
  BottomTabNavigationProp<TabParamList, 'Profile'>,
  CompositeNavigationProp<
    StackNavigationProp<StackParamList>,
    DrawerNavigationProp<DrawerParamList>
  >
>;

hook을 이용한 annotate

useNavigation

const navigation = useNavigation<ProfileScreenNavigationProp>();

useRoute

const route = useRoute<ProfileScreenRouteProp>();

실습 (적용해보기)

  • 상위 stackNavigator : "HomeTabs", "MainList", "ContentDetail
  • 차상위 bottomTabNavigator: "Home", "ChattingList", "LikedContents", "Setting"

실제 구현 코드

1. 상위 Navigation 파일에서 각 스크린이 가지는 param 구조를 포함하는 StackNavigator를 생성 및 적용

//RootNavigator.tsx
...
//하단의 type 적용을 받는 stackNavigator 생성
const Stack = createStackNavigator<RootStackParamList>();

interface Props {}

export default function RootNavigator({}: Props): ReactElement {
  return (
    <Stack.Navigator initialRouteName="HomeTabs" headerMode="float">
      <Stack.Screen
        name="HomeTabs"
        component={BottomTabNavigator}
      />
      <Stack.Screen
        name="MainList"
        component={MainListScreen}
      />
      <Stack.Screen
        name="ContentDetail"
        component={ContentDetailScreen}
      />
    </Stack.Navigator>
  );
}
// undefined는 해당 스크린에 넘겨받는 params가 없다는 의미
export type RootStackParamList = {
  HomeTabs: undefined;
  MainList: undefined;
  ContentDetail: {contentId: number};
};

2. 차상위 Navigator인 BottomTabNavigator에서 StackNavigato와 동일하게 각 스크린이 포함하는 params 구조를 포함하는TabNavigator를 생성

// BottomTabNavigator.tsx

...
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
...
import {NavigatorScreenParams, useTheme} from '@react-navigation/native';
import {RootStackParamList} from './RootNavigator';

const Tab = createBottomTabNavigator<TabNavigatorParamList>();
...
export default function BottomTabNavigator({}: Props): ReactElement {
..
  return (
    <Tab.Navigator initialRouteName={'Home'}>
      {bottomTabInfos.map(EachTab => {
        return (
          <Tab.Screen
            key={EachTab.id}
            name={EachTab.name}
            component={EachTab.component}
            options={{
              tabBarLabel: EachTab.tabBarLabel,
              tabBarIcon: () => (
                <Icon name={EachTab.iconName} size={30} color={theme} />
              ),
            }}
          />
        );
      })}
    </Tab.Navigator>
  );
}
C
export type TabNavigatorParamList = {
  Home: undefined;
  ChattingList: undefined;
  LikedContent: undefined;
  Setting: undefined;
};

3. 최하위의 해당 스크린에서는 CompositeNavigationProp을 이용하여 첫번째 인자로 BottomTabNavigator의 PramList를 이용한 NavigationProp을, 두번째 인자로 상위 StackNavigator에서의 RootStackParamList를 넣어 모두 포함시킬 수 있도록 구현

//MainMapScreen.tsx
...
import {CompositeNavigationProp} from '@react-navigation/native';
import {StackNavigationProp} from '@react-navigation/stack';
import {BottomTabNavigationProp} from '@react-navigation/bottom-tabs';
import {TabNavigatorParamList} from '../../../navigators/BottomTabNavigator';
import {RootStackParamList} from '../../../navigators/RootNavigator';
...

interface Props {
  navigation: CompositeNavigationProp<
    BottomTabNavigationProp<TabNavigatorParamList, 'Home'>,
    StackNavigationProp<RootStackParamList>
  >;
}

export default function MainMapScreen({navigation}: Props): ReactElement {
...
  navigation.navigate("")
...
 
}

4. 결과(효과)

  • StackNavigator의 navigtion prop과 BottomTabNavigator의 navigation prop을 모두 포함하여 최하위 스크린에서 사용가능
  • 최하위 레벨의 스크린에서 부모레벨 혹은 동일 레벨의 스크린 모두를 자유롭게 이동할 수 있음
profile
21c Carpenter

0개의 댓글