같은 구조를 만들고 싶은 누군가에게 도움이 되길 바라며 글을 작성해본다.
버튼을 클릭하면 화면이 덮어씌워지고 뒤로가면 나갈 수 있는 구조로 만들고 싶었다. 여기서 새 식단 추가하기는 여러 탭에서 사용하고 싶었기 때문에, 탭 안에서 일반 적인 화면으로 하면 안될 것 같았다.
여러 예시 코드를 찾아가 스택안에 탭이 있는 구조로 만드니 성공할 수 있었다. 스택안에 탭이 있는 구조인 네비게이션 타입스크립트를 찾기 어려웠기 때문에 예시 코드가 도움이 되길 바란다.
💻클릭했을 때 띄울 새로운 화면.tsx
원하는 화면을 하나 만들면 된다.
꼭 navigation를 props로 받을 필요는 없다. 나는 헤더와 뒤로가기를 위해 받아왔다.
타입은 StackScreenProps<스택 탭 리스트 타입, 현재 컴포넌트 타입>
덤으로 아래 setOptions 방식으로 헤더에 버튼을 추가할 수 있었다. 이렇게 하니 눌렀을 때 어떤 행동을 할지 함수를 넘겨주는 것이 편했다.
import { StackScreenProps } from "@react-navigation/stack";
import { RootStackParamList } from "../../App";
import { NEW_DIET } from "../constants/screenName";
type PropsType = StackScreenProps<RootStackParamList, typeof NEW_DIET>;
export default function NewDiet({ navigation }: PropsType) {
// 완료버튼
useEffect(() => {
navigation.setOptions({
headerRight: () => <HeaderDone onPress={() => saveDiet(dietNewSelectedImage)} />,
});
}, [dietNewSelectedImage]);
// 완료
const saveDiet = () => {
// 이것저것 처리하는 내용
navigation.goBack();
};
return (
<View>
<Text>클릭했을 때 띄울 화면</Text>
</View>
);
}
💻 Tabs.tsx
Bottom Tabs가 담긴 컴포넌트를 만들어준다. 본인은 이미 탭을 만든 상태에서 스택만 추가했기 때문에 분리만 했다.
type에서 TODAY같은 대분자는 string을 저장해둔 constants일 뿐이니 신경쓰지 않아도 된다.
타입을 지정하려면 creatBottomTabNavigator를 실행할 때부터 <RootTabParamList>로 지정해주어야 오류가 생기지 않았다.
RootTabParamList는 탭 목록이 담긴타입니다. props가 있으면 타입이 추가된다고 했지만 나는 없어서 undefined로 했다.
export const TODAY = "오늘";
import React from "react";
import MaterialIcons from "@expo/vector-icons/MaterialIcons";
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
import { DIET, EXERCISE, PEDOMETER, TODAY, WEIGHT } from "../constants/screenName";
import { theme } from "../style/theme";
import Today from "../screen/Today";
import Weight from "../screen/Weight";
import Diet from "../screen/Diet";
import Pedometer from "../screen/Pedometer";
import Exercise from "../screen/Exercise";
export type RootTabParamList = {
[TODAY]: undefined;
[WEIGHT]: undefined;
[DIET]: undefined;
[PEDOMETER]: undefined;
[EXERCISE]: undefined;
};
const Tab = createBottomTabNavigator<RootTabParamList>();
export default function Tabs() {
return (
<Tab.Navigator
screenOptions={({ route }) => ({
tabBarIcon: ({ focused, color, size }) => {
let iconName: IconName = "home";
if (route.name == TODAY) {
iconName = "home";
} else if (route.name == WEIGHT) {
iconName = "monitor-weight";
} else if (route.name == DIET) {
iconName = "local-dining";
} else if (route.name == PEDOMETER) {
iconName = "directions-walk";
} else if (route.name == EXERCISE) {
iconName = "fitness-center";
}
return <MaterialIcons name={iconName} size={24} color={color} />;
},
tabBarActiveTintColor: theme.color.primary[500],
tabBarInactiveTintColor: theme.color.grey[700],
})}
>
<Tab.Screen name={TODAY} component={Today} options={{ headerShown: false }} />
<Tab.Screen name={WEIGHT} component={Weight} options={{ headerShown: false }} />
<Tab.Screen name={DIET} component={Diet} options={{ headerShown: false }} />
<Tab.Screen name={PEDOMETER} component={Pedometer} options={{ headerShown: false }} />
<Tab.Screen name={EXERCISE} component={Exercise} options={{ headerShown: false }} />
</Tab.Navigator>
);
}
💻 App.tsx
App에서 위에서만든 Stack Navigation을 사용해주었다.
여기서 스택을 'Tabs(위에서 만든 Bottom Tabs기 담긴 컴포넌트)'와 'NewDiet(처음에 만든 클릭했을 때 새로 띄울 화면)'을 넣어주었다.
컴포넌트 구조를 시각적으로 표현하면 다음과 같다. 스택을 탭이 감싸고 있는 구조다.
타입은 Bottom tabs에서 했던 것과 비슷하게 List 타입을 지정해주주고 createStackNavigator를 만들때 지정해준다. 만든다.
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
import NewDiet from "./src/components/NewDiet";
import Tabs from "./src/components/Tabs";
export type RootStackParamList = {
[TABS]: undefined;
[NEW_DIET]: undefined;
};
const Stack = createStackNavigator<RootStackParamList>();
export default function App() {
return (
<NavigationContainer>
<Stack.Navigator initialRouteName={TABS}>
<Stack.Screen name={TABS} component={Tabs} options={{ headerShown: false }} />
<Stack.Screen name={NEW_DIET} component={NewDiet} />
</Stack.Navigator>
</NavigationContainer>
);
}
💻 Today.tsx
클릭하는 버튼이 있는 화면이다. 클릭하면 해당 스택네비게이션 이름을 이용해 navigation 해준다.
탭 네비에션에서 스택 네비게이션을 호출하기 때문에 타입이 가장 복잡하다.
CompositeScreenProps<바텀 타입, 위 스택 타입>를 이용해 타입을 지정해주어야 했다.
이동하는 것은 navigate함수에 화면 이름을 입력하면 간단하게 이동된다.
import { Text, TouchableOpacity, View } from "react-native";
import { StackScreenProps } from "@react-navigation/stack";
import { NEW_DIET, TODAY } from "../constants/screenName";
import { BottomTabScreenProps } from "@react-navigation/bottom-tabs";
import { RootTabParamList } from "../components/Tabs";
import { CompositeScreenProps } from "@react-navigation/native";
import { RootStackParamList } from "../../App";
type PropsType = CompositeScreenProps<
BottomTabScreenProps<RootTabParamList, typeof TODAY>,
StackScreenProps<RootStackParamList>
>;
export default function Today({ navigation }: PropsType) {
return (
<View>
{/* 다른 화면들 ... */}
<TouchableOpacity
onPress={() => navigation.navigate(NEW_DIET)}
>
<Text>새 식단 추가하기</Text>
</TouchableOpacity>
{/* ... 다른 화면들 -- */}
</View>
);
}
https://youtu.be/s7ackFpN-GU?si=J7FrTUskiZGDsxOy
https://reactnavigation.org/docs/typescript/
혹시라도 더 자세한 코드를 원한다면 댓글로 알려주세요.