본문 바로가기
Frontend/React Native

React Native 2-1. 카카오톡 친구목록 클론코딩(3): expo 기본앱에서 tabbar 커스텀 하기

by 디스코비스킷 2025. 3. 13.
반응형

리액트네이티브 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(파일이름)으로 이동하는것같음.

expo 기본앱의 기본탭
원하는 탭의 모습

일단 이렇게 탭메뉴 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을 버튼에 주기보단

리스트에 주면 좋을듯

 

 

탭메뉴완성

왜인지 탭페이지로 정상적으로 이동하지 않음

다시 한번 확인해봐야겠다. 

반응형

최근댓글

최근글

© Copyright 2024 ttutta