리액트네이티브 21년 강의를 듣다보니,
선생님이랑 expo 버전이 달라서 탭라우팅?도 들어가고 프로젝트 구조가 많이 달라서
뜯어고치느라 진도가 좀 늦어졌다..
어제 강의멈추고 혼자 해결 하느라 시간 다 보냈는데
기록을 안하니 어디서 막혀서 어떻게 해결했는지 잊을 것 같다.
강의에서는 탭을 리액트에서처럼
일반 컴포넌트만들고 app.js에서 그냥 임포트한다면
나는 기본탭을 삭제하고 어떻게 넣어야될지부터 찾아야했다.
탭 레이아웃
다행히 그 해결책은 공식문서에서 찾을 수 있었다.
https://docs.expo.dev/router/advanced/custom-tabs/
app/(tabs)/_layout.tsx
import { Tabs, TabList, TabTrigger, TabSlot } from 'expo-router/ui'; // Defining the layout of the custom tab navigator export default function Layout() { return ( <Tabs> <TabSlot /> <TabList> <TabTrigger name="home" href="/"> <Text>Home</Text> </TabTrigger> <TabTrigger name="article" href="/article"> <Text>Article</Text> </TabTrigger> </TabList> </Tabs> ); }
TabSlot은 해당탭의 페이지를 보여주는 부분같고
밑에서 탭트리거가 리액트에서 라우터와 생긴게 비슷한것같다.
탭트리거를 누르면 탭슬랏이 해당 href(파일이름)으로 이동하는것같음.


일단 이렇게 탭메뉴 4개에
카카오톡와 최대한 비슷한 아이콘(내장 아이콘 패키지 이용 https://icons.expo.fyi/Index),
active시 색이 채워진 아이콘, 기본은 outline 아이콘
이렇게 만들것!
TabButton 컴포넌트를 먼저 만들어보겠다.
TabButton 컴포넌트
이것이 필요함.
- 지금 선택된 상태인지,
- 아이콘은 두종류 사용할건데 Ionicons인지 Fontisto인지,
- active시 아이콘이름,
- inactive 아이콘이름
- onPress 함수,
- children 텍스트(안보이게 할거임)
강의랑 다르게 또 typescript 기본앱이라..
또 컴포넌트 props에 타입이 필요함..
그래서 좀 prop을 아이콘종류별로 다르게 또 써야됐다.
app/(tabs)/_layout.tsx
<TabTrigger name="home" href="/"> <TabButton isSelected={selectedTabId === 0} onPress={() => setSelectedTabId(0)} isIconFontisco={false} activeIconName={"person"} inactiveIconName={"person-outline"} > Home </TabButton> </TabTrigger>
iconName을 string으로 쓰면
아이콘요소에서 name이랑 타입이 안맞대서
activeIconName: keyof typeof Ionicons.glyphMap | keyof typeof Fontisto.glyphMap; inactiveIconName: keyof typeof Ionicons.glyphMap | keyof typeof Fontisto.glyphMap;
이렇게 썼는데,
name에서 이게 iconname이
Ionicons일수도 있고 Fontisto있어서 두개 같이 쓰려고했는데
또 타입이 다르대서 안들어감..

activeIoniconName?: keyof typeof Ionicons.glyphMap; inactiveIoniconName?: keyof typeof Ionicons.glyphMap; activeFontistoName?: keyof typeof Fontisto.glyphMap; inactiveFontistoName?: keyof typeof Fontisto.glyphMap;
isIconFontisco prop은 없어도될듯
4개나 있지만.. .optional하게 사용
(더 좋은 방법이 있을런지 나중에 찾아봐야되겠다... )
{activeFontistoName && inactiveFontistoName ? ( <Fontisto name={isSelected ? activeFontistoName : inactiveFontistoName} size={24} color="grey" /> ) : ( <Ionicons name={isSelected ? activeIoniconName : inactiveIoniconName} size={24} color="grey" /> )}
완성
app/(tabs)/_layout.tsx
import { TabList, Tabs, TabSlot, TabTrigger } from "expo-router/ui"; import { useState } from "react"; import { StyleSheet } from "react-native"; import TabButton from "../../components/TabButton"; // Defining the layout of the custom tab navigator export default function Layout() { const [selectedTabId, setSelectedTabId] = useState(0); return ( <Tabs> <TabSlot style={{ flex: 1, }} /> <TabList style={{ width: "100%", backgroundColor: "white", height: 70, flexDirection: "row", justifyContent: "space-around", }} > <TabTrigger name="home" href="/" style={styles.TabTrigger}> <TabButton isSelected={selectedTabId === 0} onPress={() => setSelectedTabId(0)} activeIoniconName={"person"} inactiveIoniconName={"person-outline"} > Home </TabButton> </TabTrigger> <TabTrigger name="article" href="/(tabs)/chat" style={styles.TabTrigger} > <TabButton isSelected={selectedTabId === 1} onPress={() => setSelectedTabId(1)} activeIoniconName={"chatbubble-sharp"} inactiveIoniconName={"chatbubble-outline"} > Chat </TabButton> </TabTrigger> <TabTrigger name="article" href="/(tabs)/explore" style={styles.TabTrigger} > <TabButton isSelected={selectedTabId === 2} onPress={() => setSelectedTabId(2)} activeFontistoName={"hashtag"} inactiveFontistoName={"hashtag"} > explore </TabButton> </TabTrigger> <TabTrigger name="article" href="/(tabs)/more" style={styles.TabTrigger} > <TabButton isSelected={selectedTabId === 3} onPress={() => setSelectedTabId(3)} activeIoniconName={"ellipsis-horizontal"} inactiveIoniconName={"ellipsis-horizontal-outline"} > more </TabButton> </TabTrigger> </TabList> </Tabs> ); } const styles = StyleSheet.create({ TabTrigger: { alignItems: "center", justifyContent: "center", } });
components/TabButton.tsx
import { Fontisto, Ionicons } from '@expo/vector-icons'; import React, { useState } from 'react'; import { Text, TouchableOpacity } from 'react-native'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; interface TabButtonProps { isSelected: boolean; onPress: () => void; activeIoniconName?: keyof typeof Ionicons.glyphMap; inactiveIoniconName?: keyof typeof Ionicons.glyphMap; activeFontistoName?: keyof typeof Fontisto.glyphMap; inactiveFontistoName?: keyof typeof Fontisto.glyphMap; children?: React.ReactNode; } const TabButton = ({ isSelected, activeIoniconName, inactiveIoniconName, activeFontistoName, inactiveFontistoName, onPress, children, }: TabButtonProps) => { const [selectedTabId, setSelectedTabId] = useState(0); const insets = useSafeAreaInsets(); return ( <TouchableOpacity onPress={onPress} style={{ paddingBottom: insets.bottom,}} > {activeFontistoName && inactiveFontistoName ? ( <Fontisto name={isSelected ? activeFontistoName : inactiveFontistoName} size={24} color="grey" /> ) : ( <Ionicons name={isSelected ? activeIoniconName : inactiveIoniconName} size={24} color="grey" /> )} <Text style={{ fontSize: 0,width:0, height:0, overflow: "hidden" }}>{children}</Text> </TouchableOpacity> ); }; export default TabButton
safeArea paddingBottom을 버튼에 주기보단
리스트에 주면 좋을듯

왜인지 탭페이지로 정상적으로 이동하지 않음
다시 한번 확인해봐야겠다.
'Frontend > React Native' 카테고리의 다른 글
React Native 2-1. 카카오톡 친구목록 클론코딩(5): 친구목록 컴포넌트 (1) | 2025.03.15 |
---|---|
React Native 2-1. 카카오톡 친구목록 클론코딩(4): 친구목록 상단 컴포넌트들 (0) | 2025.03.15 |
React Native 2-1. 카카오톡 친구목록 클론코딩(2) (0) | 2025.03.10 |
React Native 2-1. 카카오톡 친구목록 클론코딩(1) (0) | 2025.03.10 |
React Native Expo실행시 iOS Simulator 실행안됨 해결 (0) | 2025.03.06 |