[react-native] typescript 환경에서 navigation 사용
이번에는 가장 일반적으로 사용하는 Stack Navigation을 구현하는 컴포넌트를 typeScript를 사용해서 구성해보려고 한다.
기본적으로 아래와 같이 RootStack을 구성할 수 있는데,
◎App.tsx
import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import RootStack from './screens/RootStack.tsx';
const App = () => {
return (
<NavigationContainer>
<RootStack />
</NavigationContainer>
);
};
export default App;
◎screens/RootStack.tsx
import React from 'react';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import {useNavigation} from '@react-navigation/native';
import {Button, Text, View} from 'react-native';
const Stack = createNativeStackNavigator();
const HomeScreen = () => {
const navigation = useNavigation();
const onPress = () => {
navigation.navigate('Detail');
};
return (
<View>
<Text>Home</Text>
<Button title="Open Detail" onPress={onPress} />
</View>
);
};
const DetailScreen = () => {
return (
<View>
<Text>Detail</Text>
</View>
);
};
const RootStack = () => {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
);
};
export default RootStack;
위와 같이 간단하게 RootStack을 javascript 환경에서와 비슷하게 작성을 해주면, javascript환경에서는 문제가 전혀 없지만 ts환경에서는 navigation.navigate('Detail')에서 Detail 부분에 오류가 뜨게 된다.
ts에서는 네이티브 스택 네비게이션을 사용할 때, 어떤 화면에 어떤 파라미터가 필요한지를 정의하는 StackParamList 타입을 정의해줘야 한다.
즉, 아래와 같이 화면을 위한 타입을 따로 선언해줘야 한다.
◎screens/RootStack.tsx
import React from 'react';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
} from '@react-navigation/native-stack';
import {useNavigation} from '@react-navigation/native';
import {Button, Text, View} from 'react-native';
/* Stack Param List */
type RootStackParamList = {
Home: undefined;
Detail: {
id: number;
};
};
export type RootStackNavigationProp =
NativeStackNavigationProp<RootStackParamList>;
const Stack = createNativeStackNavigator<RootStackParamList>();
const HomeScreen = () => {
const navigation = useNavigation<RootStackNavigationProp>();
const onPress = () => {
navigation.navigate('Detail', {id: 1});
};
return (
<View>
<Text>Home</Text>
<Button title="Open Detail" onPress={onPress} />
</View>
);
};
const DetailScreen = () => {
return (
<View>
<Text>Detail</Text>
</View>
);
};
const RootStack = () => {
return (
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
);
};
export default RootStack;
ts를 사용하는 환경에서는 NativeStackNavigationProp을 사용하여 선언된 NavigationProp이 useNavigation에 Generic으로 설정되어 있지 않으면 navigation.push, navigation.pop 등의 함수를 사용할 수 없다.
- how about useRoute
useRoute를 사용할 때는, RouteProp을 사용하여 선언한 타입을 Generic으로 설정해줘야 한다.
◎screens/RootStack.tsx
import React from 'react';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
} from '@react-navigation/native-stack';
import {RouteProp, useNavigation, useRoute} from '@react-navigation/native';
import {Button, Text, View} from 'react-native';
/* Stack Param List */
type RootStackParamList = {
Home: undefined;
Detail: {
id: number;
};
};
export type RootStackNavigationProp =
NativeStackNavigationProp<RootStackParamList>;
const Stack = createNativeStackNavigator<RootStackParamList>();
(...)
type DetailScreenRouteProp = RouteProp<RootStackParamList, 'Detail'>;
const DetailScreen = () => {
const {params} = useRoute<DetailScreenRouteProp>();
return (
<View>
<Text>Detail {params.id}</Text>
</View>
);
};
(...)
export default RootStack;
- 네비게이션 감싸기
이번에는 좀 더 나아가서 Stack Navigation 내부에 Bottom Tab Navigation을 넣어야 하는 상황을 한번 만들어보자.
Bottom Tab Navigation에서 그 상위에 있는 RootStack에 있는 컴포넌트들에게도 접근이 가능하도록 해야 하기 때문에 아래와 같이 CompositeNavigationProp을 사용한다.
◎screens/MainTab.tsx
import React from 'react';
import {BottomTabNavigationProp, createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {CompositeNavigationProp, useNavigation} from '@react-navigation/native';
import {RootStackNavigationProp} from './RootStack.tsx';
import {Button, Text, View} from 'react-native';
type MainTabParamList = {
Home: undefined;
Account: undefined;
};
export type MainTabNavigationProp = CompositeNavigationProp<
RootStackNavigationProp,
BottomTabNavigationProp<MainTabParamList>
>;
/* 추후 Root Stack 에서 Main Tab에 있는 스크린에 접근이 가능하도록 export*/
export type MainTabNavigationScreenParams = BottomTabNavigationProp<MainTabParamList>;
const Tab = createBottomTabNavigator<MainTabParamList>();
const HomeScreen = () => {
const navigation = useNavigation<MainTabNavigationProp>();
const onPress = () => {
navigation.navigate('Detail', {id: 1});
};
return (
<View>
<Text>Home</Text>
<Button title="Open Detail" onPress={onPress} />
</View>
);
};
const AccountScreen = () => {
return (
<View>
<Text>Account</Text>
</View>
);
};
const MainTab = () => {
return (
<Tab.Navigator>
<Tab.Screen name="Account" component={AccountScreen} />
<Tab.Screen name="Home" component={HomeScreen} />
</Tab.Navigator>
);
};
export default MainTab;
◎screens/RootStack.tsx
import React from 'react';
import {
createNativeStackNavigator,
NativeStackNavigationProp,
} from '@react-navigation/native-stack';
import {RouteProp, useNavigation, useRoute} from '@react-navigation/native';
import {Button, Text, View} from 'react-native';
import MainTab, {MainTabNavigationScreenParams} from './MainTab.tsx';
/* Stack Param List */
type RootStackParamList = {
MainTab: MainTabNavigationScreenParams;
Detail: {
id: number;
};
};
export type RootStackNavigationProp =
NativeStackNavigationProp<RootStackParamList>;
const Stack = createNativeStackNavigator<RootStackParamList>();
const HomeScreen = () => {
const navigation = useNavigation<RootStackNavigationProp>();
const onPress = () => {
navigation.navigate('Detail', {id: 1});
};
return (
<View>
<Text>Home</Text>
<Button title="Open Detail" onPress={onPress} />
</View>
);
};
type DetailScreenRouteProp = RouteProp<RootStackParamList, 'Detail'>;
const DetailScreen = () => {
const {params} = useRoute<DetailScreenRouteProp>();
return (
<View>
<Text>Detail {params.id}</Text>
</View>
);
};
const RootStack = () => {
return (
<Stack.Navigator>
<Stack.Screen
name="MainTab"
component={MainTab}
options={{headerShown: false}}
/>
<Stack.Screen name="Detail" component={DetailScreen} />
</Stack.Navigator>
);
};
export default RootStack;
navigationProp, propsList... 조금 복잡해 보이지만 천천히 코드를 보고 익혀보면 될 거 같다.
MainTab 내부에서 만약에 또 다른 Navigator를 사용할 일이 생기면 마찬가지로 아래와 같이 CompositeNavigationProp을 사용하면 된다.
type AccountStackParamList = {
Account: undefined;
Setting: undefined;
};
export type AccountStackNavigationProp = CompositeNavigationProp<
MainTabNavigationProp,
StackNavigationProp<AccountStackParamList>
>;
끝!