본문 바로가기
대외활동/멋쟁이사자처럼_프론트엔드 12기

React Native로 하단탭 네비게이션 만들기 DRACONIST

by 피스타0204 2025. 2. 18.

 

1. 필요한 라이브러리를 설치합니다.

라이브러리 이름

@react-navigation/native React Native에서 네비게이션 기능을 제공
@react-navigation/bottom-tabs 하단 탭 네비게이션을 위한 패키지
react-native-screens 네이티브 화면 전환 성능 향상
react-native-safe-area-context 안전 영역(노치, 상태바 등) 처리
react-native-vector-icons 아이콘 사용을 위한 라이브러리
react-native-gesture-handler 제스처(스와이프, 터치 등) 지원
react-native-reanimated 애니메이션 성능 최적화 라이브러리
npm install @react-navigation/native @react-navigation/bottom-tabs react-native-screens react-native-safe-area-context react-native-vector-icons react-native-gesture-handler react-native-reanimated

 

더보기
더보기

react-native-reanimated는 네이티브 모듈을 활용하여 애니메이션을 최적화하는 라이브러리입니다. 이 라이브러리는 JIT(Just-In-Time) 컴파일러를 사용하지 않는 Hermes 엔진과 함께 작동할 때, Babel 플러그인을 추가적으로 설정해야 정상적으로 동작합니다.

react-native-reanimated 추가 설정

react-native-reanimated는 추가적인 설정이 필요합니다.
babel.config.js 파일을 열고 아래 내용을 추가하세요.

module.exports = {
  presets: ['module:metro-react-native-babel-preset'],
  plugins: ['react-native-reanimated/plugin'],
};

 

2. react-navigation/bottom-tabs 라이브러리를 이용하여 하단탭 네비게이션을 생성합니다.

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { MaterialCommunityIcons } from '@expo/vector-icons';

const Tab = createBottomTabNavigator();

const TabNavigation = () => {
    return (
        <Tab.Navigator>
            <Tab.screen></Tab.screen>
            <Tab.screen></Tab.screen>
            <Tab.screen></Tab.screen>
        </Tab.Navigator>
    )
}

 

먼저 createBottomTavNavigator함수로 하단탭을 생성하고 이를 Tab 변수에 저장합니다. 

Tab.Navigator 로 내비게이터를 설정하고 Tab.screen으로 각각 이동할 화면의 탭(화면에서는 버튼으로 보임)을 만듭니다.

 

3. screen 페이지를 만듭니다.

더보기
더보기
import React from 'react';
import { Button, StatusBar, Platform } from 'react-native';
import styled from 'styled-components/native';
import { SafeAreaProvider, SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';

const Container = styled(SafeAreaView)`
  flex: 1;
  align-items: center;
  background-color: #ffffff;
`;

const StyledText = styled.Text`
  font-size: 30px;
  margin-bottom: 10px;
`;

const Home = ({ navigation }) => {
  const insets = useSafeAreaInsets();

  return (
    <SafeAreaProvider>
      <StatusBar barStyle="dark-content" backgroundColor="#ffffff" />
      <Container style={{ paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : insets.top }}>
        <StyledText>Home</StyledText>
        <Button title="Go to the List Screen" onPress={() => navigation.navigate('List')} />
      </Container>
    </SafeAreaProvider>
  );
};

export default Home;

 

import React from 'react';
import { Button, StatusBar, Platform } from 'react-native';
import styled from 'styled-components/native';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';

// 스타일 정의
const Container = styled(SafeAreaView)`
  flex: 1;
  align-items: center;
  justify-content: center;
  background-color: #ffffff;
`;

const StyledText = styled.Text`
  font-size: 30px;
  margin-bottom: 10px;
`;

// 🚀 Mypage 컴포넌트
const Mypage = ({ navigation }) => {
  const insets = useSafeAreaInsets();

  return (
    <>
      <StatusBar barStyle="dark-content" backgroundColor="#ffffff" />
      <Container style={{ paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : insets.top }}>
        <StyledText>Mypage</StyledText>
        <Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
      </Container>
    </>
  );
};
  export default Mypage;
import React from 'react';
import { Button, StatusBar, Platform } from 'react-native';
import styled from 'styled-components/native';
import { SafeAreaView, useSafeAreaInsets } from 'react-native-safe-area-context';

const Container = styled(SafeAreaView)`
  flex: 1;
  align-items: center;
  justify-content: center;
  background-color: #ffffff;
`;

const StyledText = styled.Text`
  font-size: 30px;
  margin-bottom: 10px;
`;

// 🚀 Travel 컴포넌트
const Travel = ({ navigation }) => {
  const insets = useSafeAreaInsets();

  return (
    <>
      <StatusBar barStyle="dark-content" backgroundColor="#ffffff" />
      <Container style={{ paddingTop: Platform.OS === 'android' ? StatusBar.currentHeight : insets.top }}>
        <StyledText>Travel</StyledText>
        <Button title="Go to Home" onPress={() => navigation.navigate('Home')} />
      </Container>
    </>
  );
};

export default Travel;

 

 

 

4. screens에 각 컴포넌트를 부여하고, 이름, 라벨 정하기

const TabNavigation = () => {
  return (
    <Tab.Navigator initialRouteName="Home">
      <Tab.Screen
        name="Home"
        component={Home}
      />
      <Tab.Screen
        name="Travel"
        component={Travel}
      />
      <Tab.Screen
        name="Mypage"
        component={Mypage}
       
      />
    </Tab.Navigator>
  );
};

여기서 각 컴포넌트에 설정한 name에 따라 intialRouteName 처음 들어갔을 때 눌린 화면을 선택할 수 있습니다.

 

5. 눌렸는지 여부에 따라 다른 아이콘 뜨게 하기

const TabNavigation = () => {
  return (
    <Tab.Navigator initialRouteName="Settings">
      <Tab.Screen
        name="Mail"
        component={Mail}
        options={{
          tabBarLabel: 'Inbox',
          tabBarIcon: props => (
            <TabIcon
              {...props}
              name={props.focused ? 'email' : 'email-outline'}
            />
          ),
        }}
      />
      <Tab.Screen
        name="Meet"
        component={Meet}
        options={{
          tabBarIcon: props => (
            <TabIcon
              {...props}
              name={props.focused ? 'video' : 'video-outline'}
            />
          ),
        }}
      />
      <Tab.Screen
        name="Settings"
        component={Settings}
        options={{
          tabBarIcon: props => (
            <TabIcon
              {...props}
              name={props.focused ? 'settings' : 'settings-outline'}
            />
          ),
        }}
      />
    </Tab.Navigator>
  );
};

 

focused여부에 따라 다른 아이콘을 보여줄 수 있습니다.

 

6. Navigator 스타일 주기

    <Tab.Navigator
      initialRouteName="Home"
      screenOptions={{
        tabBarActiveTintColor: '#000000',
        tabBarInactiveTintColor: '#1D1D1F',
        tabBarStyle: {
          backgroundColor: '#ffffff', // 탭 바 배경색 설정
          borderTopColor: '#ffffff', // 탭 바 경계선 색상
          borderTopWidth: 1, // 탭 바 경계선 두께
          height: 70,
          paddingTop: 5,
        },
      }}
    >

 

Navigator에서는 전체 적인 navigation에 스타일을 줄 수 있습니다.

 

TabIcon넣기

// tab icon의 크기, 색상, 이름 format 맞추기
const TabIcon = ({ source, size }) => {
  return (
    <Image
      source={source}
      style={{ width: size, height: size, resizeMode: 'contain' }}
    />
  );
};

 

아이콘을 일관되게 렌더링할 수 있도록 공통 TabIcon 컴포넌트를 정의하고 이를 사용합니다.

 

7. 라벨 focused에 따라 바꾸기?

라벨은 focused여부에 따라 바꾸기 어렵습니다. Text를 이용하여 커스터마이즈 하는 방식을 사용하면 focused 여부에 따라 글자색이나 크기, fontWeight등을 설정할 수 있습니다.

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Image, Text, TouchableOpacity, View } from 'react-native';
import Home from '../screens/Home';
import Travel from '../screens/Travel';
import Mypage from '../screens/Mypage';
import { theme } from "../theme";

const Tab = createBottomTabNavigator();

// tab icon의 크기, 색상, 이름 format 맞추기
const TabIcon = ({ source, size }) => {
  return (
    <Image
      source={source}
      style={{ width: size, height: size, resizeMode: 'contain' }}
    />
  );
};

const BottomTab = () => {
  return (
    <Tab.Navigator
      initialRouteName="Home"
      screenOptions={{
        tabBarActiveTintColor: '#000000',
        tabBarInactiveTintColor: '#1D1D1F',
        tabBarStyle: {
          backgroundColor: '#ffffff', // 탭 바 배경색 설정
          borderTopColor: '#ffffff', // 탭 바 경계선 색상
          borderTopWidth: 1, // 탭 바 경계선 두께
          height: 70,
          paddingTop: 5,
        },
      }}
    >
      <Tab.Screen
        name="Home"
        component={Home}
        options={{
          tabBarIcon: ({ focused }) => (
            <TabIcon
              source={
                focused
                  ? require('../assets/icons/bottom-tab-home-filled.png')
                  : require('../assets/icons/bottom-tab-home.png')
              }
              size={30}
            />
          ),
          tabBarButton: (props) => (
            <TouchableOpacity {...props}>
              <View style={{ alignItems: 'center' }}>
                <TabIcon
                  source={
                    props.accessibilityState.selected
                      ? require('../assets/icons/bottom-tab-home-filled.png')
                      : require('../assets/icons/bottom-tab-home.png')
                  }
                  size={30}
                />
                <Text
                  style={{
                    fontSize: 12,
                    color: props.accessibilityState.selected
                      ? '#000000' // 선택된 탭 글자 색
                      : '#1D1D1F', // 비선택된 탭 글자 색
                    fontWeight:props.accessibilityState.selected
                        ? 'bold'
                        : 'medium',
                        paddingTop: 5,
                  }}
                ></Text>
              </View>
            </TouchableOpacity>
          ),
        }}
      />
      <Tab.Screen
        name="Travel"
        component={Travel}
        options={{
          tabBarIcon: ({ focused }) => (
            <TabIcon
              source={
                focused
                  ? require('../assets/icons/bottom-tab-travel-filled.png')
                  : require('../assets/icons/bottom-tab-travel.png')
              }
              size={30}
            />
          ),
          tabBarButton: (props) => (
            <TouchableOpacity {...props}>
              <View style={{ alignItems: 'center' }}>
                <TabIcon
                  source={
                    props.accessibilityState.selected
                      ? require('../assets/icons/bottom-tab-travel-filled.png')
                      : require('../assets/icons/bottom-tab-travel.png')
                  }
                  size={30}
                />
                <Text
                  style={{
                    fontSize: 12,
                    color: props.accessibilityState.selected
                      ? '#000000'
                      : '#1D1D1F',
                      fontWeight:props.accessibilityState.selected
                      ? 'bold'
                      : 'medium',
                      paddingTop: 5,
                  }}
                >
                  여행기록
                </Text>
              </View>
            </TouchableOpacity>
          ),
        }}
      />
      <Tab.Screen
        name="Mypage"
        component={Mypage}
        options={{
          tabBarIcon: ({ focused }) => (
            <TabIcon
              source={
                focused
                  ? require('../assets/icons/bottom-tab-my-filled.png')
                  : require('../assets/icons/bottom-tab-my.png')
              }
              size={30}
            />
          ),
          tabBarButton: (props) => (
            <TouchableOpacity {...props}>
              <View style={{ alignItems: 'center' }}>
                <TabIcon
                  source={
                    props.accessibilityState.selected
                      ? require('../assets/icons/bottom-tab-my-filled.png')
                      : require('../assets/icons/bottom-tab-my.png')
                      
                  }
                  size={30}
                />
                <Text
                  style={{
                    fontSize: 12,
                    color: props.accessibilityState.selected
                      ? '#000000'
                      : '#1D1D1F',
                      fontWeight:props.accessibilityState.selected
                      ? 'bold'
                      : 'medium',
                      paddingTop: 5,
                  }}
                >
                  MY
                </Text>
              </View>
            </TouchableOpacity>
          ),
        }}
      />
    </Tab.Navigator>
  );
};

export default BottomTab;