
저번 시간에 작성했던데로, 어떻게 하면 Stack, Tab Navigator를 기반으로 Router 구조를 설정할지 알아볼 수 있었다. 가볍게 navigation.navigate() 를 설정하고, 넘어가려던 찰나 TypeScript에서의 Type 오류가 발생했다. 그러고보니까 Type은,,?
Argument types do not match parameters
Argument of type '"Login"' is not assignable to parameter of type ...
바로 navigation.navigate 에서의 인자 타입 오류였다. 생각해보면, navigation에 어떻게 type을 지정해주고, 이 인자를 어떻게 설정할지에 대한 생각이 짧았다. 단순하게 해결할 수 있을 줄 알았던 문제는 꽤 시간이 들게 되는데,,
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
NativeStackNavigationProp : navigation.navigte(...) 사용 시 navigation 객체의 타입을 정의하기 위해 사용하는 TypeScript의 타입. 주로 useNavigation의 훅이나 props.navigation을 사용할 때 타입 안정성을 제공하기 위해 사용한다.
NativeStackNavigationProp<ParamList, RouteName> 의 형태로 사용된다.
1) ParamList : 화면 이름과 그 화면에 전달되는 파라미터를 정의한 객체 (ex, RootStackParamList)
import { NativeStackNavigationProp } from '@react-navigation/native-stack';
export type RootStackParamList = {
Landing: undefined;
Login: undefined;
SignUpFunnel: undefined;
Detail: { id: number }; // 예시를 위해서 추가, id parameter 또한 전달 가능함을 명시
};
export type LandingScreenNavigationProps = NativeStackNavigationProp<
RootStackParamList,
'Landing'
>;
현재 내가 정의한 간단한 RootStackParamList이다. 현재까지 제작한 page들의 이름과 type을 undefined로 정의했다.
2) RouteName : 위 ParamList에 정의된 특정 화면 이름. 이 RouteName은 StackNavigator에서 사용되는 이름 또는 Page에서 navigate나 push 로 전달되는 이름과 반드시 일치해야한다!
해당 화면에서 사용될 수 있는 navigation의 함수들 (push, navigate 등)을 타입 안전하게 쓸 수 있도록 도와준다.
const Stack = createNativeStackNavigator<RootStackParamList>();
++ tab에서도
const Tab = createBottomTabNavigator<TabParamList>();
export default function LandingScreen({
navigation,
}: LandingScreenNavigationProps) {
return (
<View style={styles.container}>
<Image source={ImagePaths.logo} style={styles.logoImage} />
<TouchableOpacity
style={styles.button}
onPress={() => navigation.navigate('Login')}
>
<Text style={styles.buttonText}>로그인</Text>
</TouchableOpacity>
);
}
LandingScreen은 props로 navigation을 받고 있다. 이 navigation 객체의 타입을 명확히 하기 위해 LandingScreenNavigationProps 를 사용한다.
내부적으로 NativeStackNavigationProp<RootStackParamList,'Landing'> 타입을 통해 타입 안정성 확보가 가능한다.
결과적으로 .navigate('Login') 처럼 사용할 때 자동 완성, 타입 체크 등이 가능해진다.
이 두 Navigator의 혼합을 자주 사용하게 될텐데 추가적으로 작성해줘야하는 타입이 존재한다.
export type RootStackParamList = {
Landing: undefined;
Login: undefined;
SignUpFunnel: undefined;
Tab: NavigatorScreenParams<TabParamList>; // 꼭 @react-navigation/native에서 import
};
export type TabParamList = {
sanding: undefined; // test를 위해 잠시 sanding으로 바꾼 것. 동일.
};
Tab에서의 navigator type은 NavigatorScreenParams가 존재한다. 혼동할 수 있는 것은 @react-navigation/core 에도 이 Params가 존재해서, native에서 꼭 import 해와야한다.
RootStack.Navigator
├── Login
├── SignUpFunnel
└── Tab → Tab.Navigator
├── Home
├── Profile
└── ...
만약 사용자의 화면 전환이 위와 같은 구조를 띈다고 가정했을 때, RootStackParamList에 Tab이 들어가야한다.
RootStack.Navigator의 하나의 screen으로 TabNavigator를 등록해야하기 때문이다.
그러나 이 때 TabNavigator는 내부에 여러 개의 Tab.Screen이 존재하는데, 이 때문에 Tab은 단순히 undefined로 지정되는 것이 아닌, Tab.Screen들 (Home, Profile 등등) 중 하나를 선택할 수 있는 navigator임을 알려줘야 한다. 그래서 이럴 때 쓰는 타입이 바로 NavigatorScreenParams 이다.
-> Tab이라는 Stack 화면에 실제로는 TabParamList 안의 여러 화면들이 존재한다고 React Navigation이 인식하게 되는 것이다.