리액트네이티브 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 |