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

REACT NATIVE 스터디. 3주차 8단원. Navigation DRACONIST

by 피스타0204 2025. 1. 30.

모자일 애플리케이션은 일반적으로 다양한 화면이 상황에 맞게 전환되어 보입니다. 

리액트 네이티브에서는 이러한 역할을 하는 내비게이션(navigation) 기능을 지원하지 않으므로 외부 라이브러리를 사용해야 합니다.

 

실습 준비를 해봅시다.

npx create-expo-app react-native-navigation
cd react-native-navigation
npm install styled-components

 

1. 리액트 내비게이션

styled-components에서 지원하는 내비게이션의 종류는 stack navigation, tab navigation, drawer navigation 세 종류입니다.

순서대로 stack navigation, tab navigation, drawer navigation

 

 

1) 내비게이션의 기본 구조

  • Screen → 각 화면에 적용되는 일정한 틀을 만들고, name과 component를 부여하여 여러개의 서로 다른 화면들을 만들수 있다. 각 화면을 하나의 스크린이라고 한다.
  • Navigator → 여러 개의 화면(Screen)을 한번에 묶에서 관리한다.
  • NavigationContainer → 전체 내비게이션을 감싸는 컨테이너로 내비게이션의 상태와 계층 구조를 관리하는 역할을 하도록 라이브러리에서 설정해두었으므로 빼먹지 말아야 한다. NavigationContainer는 최상위 컴포넌트에 하나만 있어야 한다.

 

tab navigation과 stack navigation을 같이 사용할 수도 있는데 이럴 때에는 tabScreen안에 stacknavigation을 component로 연결합니다. 

import React from 'react';
import { View, Text, Button } from 'react-native';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';

// 1. 실제 화면 (Screen) 컴포넌트
function HomeScreen({ navigation }) {
  return (
    <View>
      <Text>홈 화면</Text>
      <Button title="상세 페이지로 이동" onPress={() => navigation.navigate('Details')} />
    </View>
  );
}

function DetailsScreen() {
  return (
    <View>
      <Text>상세 화면</Text>
    </View>
  );
}

// 2. Stack Navigator 생성
const Stack = createStackNavigator();

export default function App() {
  return (
    // 3. NavigationContainer로 감싸기
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen name="Home" component={HomeScreen} />
        <Stack.Screen name="Details" component={DetailsScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

 

2) 설정 우선 순위

react Navigation에서는 화면의 속성을 설정하는 방법이 3가지가 있습니다. 

-Navigator에 screenOptions 속성을 사용해서 모든 Screen에 같은 스타일을 적용할 수 있습니다.

<Stack.Navigator screenOptions={{ headerStyle: { backgroundColor: 'tomato' } }}>
  <Stack.Screen name="Home" component={HomeScreen} />
  <Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>

 

-Screen에 options 속성을 사용하면 특정 화면에만 스타일을 다르게 적용할 수 있습니다.

<Stack.Screen 
  name="Details" 
  component={DetailsScreen} 
  options={{ headerStyle: { backgroundColor: 'blue' } }} 
/>

 

-props로 전달된 navigation을 navigation.setOptions()를 사용해서 동적으로 수정할 수 있습니다.

function DetailsScreen({ navigation }) {
  return (
    <View>
      <Text>상세 화면</Text>
      <Button title="헤더 변경" onPress={() => navigation.setOptions({ title: '새로운 제목' })} />
    </View>
  );
}

 

navigator 단> screen 단 > props 단순으로 더 작은 범위를 선택할 수 있습니다.

작은 범위의 설정일수록 우선순위가 높으므로 만약 Screen 컴포넌트와 props를 사용하는 경우의 우선순위가 더 높습니다.


2. 스택 내비게이션

1) 화면구성

 

Stack Navigator는 각각의 화면을 "스택" 형태로 쌓아두고, 사용자가 이전 화면으로 돌아갈 때 스택에서 하나씩 꺼내는 방식입니다. 이는 화면 간의 전환, 뒤로가기등에 이용됩니다.

npm install @react-navigation/native 
npm install @react-navigation/native @react-navigation/stack react-native-gesture-handler react-native-reanimated react-native-screens react-native-safe-area-context @react-native-community/masked-view

 

이다음에 책에서는 react native CLI에서 진행하는 방법을 알려주고 있습니다. 우리는 expo를 사용하고 있으므로 해당 방법을 사용하면 NavigatorContainer inside another오류가 발생합니다.

 

Expo Router는 내부적으로 NavigationContainer를 사용하고 있습니다. 따라서 Expo Router를 사용하고 있는 앱에서 NavigationContainer를 또 사용하면 중복된 내비게이션 컨테이너가 발생하고, 이로 인해 오류가 발생합니다.

이는 app>(tabs)>_layout.tsx에 있는 아래 코드가 문제인 것으로 해당 코드를 삭제하거나(비추천), app.js에 stack으로 작성해주어 해결할 수 있습니다.

import { Stack } from 'expo-router';

//...
      <Stack>
        <Stack.Screen name="(tabs)" options={{ headerShown: false }} />
        <Stack.Screen name="+not-found" />
      </Stack>

 

우리는 두번째 방법으로 expo가 만들어놓은 Stack을 navigations폴더 없이 바로 작성하겠습니다.
화면 구성 실습)

더보기

//App.js

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import Home from './screens/Home';
import List from './screens/List';
import Item from './screens/Item';

const Stack = createStackNavigator();

const App = () => {
  return (
      <Stack.Navigator initialRouteName="Home">
        <Stack.Screen name="Home" component={Home}/>
        <Stack.Screen name="List" component={List} />
        <Stack.Screen name="Item" component={Item} />
      </Stack.Navigator>
  );
};

export default App;

 

//screens/Home.js

import React from 'react';
import { Button } from 'react-native';
import styled from 'styled-components/native';

// 스타일 정의
const Container = styled.View`
  align-items: center;
`;

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

const Home = () => {
  // 화면 렌더링
  return (
    <Container>
      <StyledText>Home</StyledText>
      <Button title="Go to the List Screen" onPress={() => console.log('Navigate to list screen')} />
    </Container>
  );
};

export default Home;

 

//screens/Item.js

import React from 'react';
import styled from 'styled-components/native';

// Properly closing the styled-components
const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
`;

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

const Item = () => {
  return (
    <Container>
      <StyledText>Item</StyledText>
    </Container>
  );
};

export default Item;


//screens/List.js

import React from 'react';
import { Button } from 'react-native';
import styled from 'styled-components/native';

const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
`;

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

const items = [
  { _id: 1, name: 'React Native' },
  { _id: 2, name: 'React Navigation' },
  { _id: 3, name: 'Hanbit' }
];

const List = () => {
  const _onPress = (item) => {
    // Handle item press
    console.log(item);
  };

  return (
    <Container>
      <StyledText>List</StyledText>
      {items.map(item => (
        <Button
          key={item._id}
          title={item.name}
          onPress={() => _onPress(item)} 
        />
      ))}
    </Container>
  );
};

export default List;

 

 

2) 화면 이동 

이번에는 화면을 이동하는 방법에 대해 알아보겠습니다. Screen 컴포넌트의 component로 지정된 컴포넌트가 우리눈에 보이는 화면입니다. 해당 component의 값으로 지정된 컴포넌트에는 props로 navigation이 전달됩니다.

# navigation 객체에는 화면 간 이동을 관리하는 메서드들이 포함되어 있습니다.

 

더보기

실습)
먼저 Home화면을 첫번째 화면으로 설정합니다.

screen들에 각각 onPress시 navigate되도록 설정합니다.
//Home.js

import { navigate } from 'expo-router/build/global-state/routing';
import React from 'react';
import { Button } from 'react-native';
import styled from 'styled-components/native';

// 스타일 정의
const Container = styled.View`
  align-items: center;
`;

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

const Home = ({navigation}) => {
  // 화면 렌더링
  return (
    <Container>
      <StyledText>Home</StyledText>
      <Button title="Go to the List Screen" onPress={() => navigation.navigate('List')} />
    </Container>
  );
};

export default Home;

 

//List.js

list에서는 특정 버튼을 누르면 해당하는 상세화면으로 이동해야 합니다.

 navigate함수를 이용하여 Item화면으로 이동하면서 props로 id와 name을 전달하여 화면을 구성합시다.

import React from 'react';
import { Button } from 'react-native';
import styled from 'styled-components/native';

const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
`;

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

const items = [
  { _id: 1, name: 'React Native' },
  { _id: 2, name: 'React Navigation' },
  { _id: 3, name: 'Hanbit' }
];

const List = ({navigation}) => {
  const _onPress = (item) => {
    navigation.navigate('Item', {id: item._id, name: item.name});
  };

  return (
    <Container>
      <StyledText>List</StyledText>
      {items.map(item => (
        <Button
          key={item._id}
          title={item.name}
          onPress={() => _onPress(item)} 
        />
      ))}
    </Container>
  );
};

export default List;

 

 //Item.js

route라는 이름으로 한번에 받아 나눠 사용해보겠습니다.

import React from 'react';
import styled from 'styled-components/native';

// Properly closing the styled-components
const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
`;

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

const Item = ({ route }) => {
    return (
      <Container>
        <StyledText>Item</StyledText>
        <StyledText>ID: {route.params.id}</StyledText>
        <StyledText>Name: {route.params.name}</StyledText>
      </Container>
    );
  };
  

export default Item;

3) 화면 꽉 채우게 만들기

import { navigate } from 'expo-router/build/global-state/routing';
import React from 'react';
import { Button } from 'react-native';
import styled from 'styled-components/native';

// 스타일 정의
const Container = styled.View`
  align-items: center;
  background-color: #ffffff;
`;

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

const Home = ({navigation}) => {
  // 화면 렌더링
  return (
    <Container>
      <StyledText>Home</StyledText>
      <Button title="Go to the List Screen" onPress={() => navigation.navigate('List')} />
    </Container>
  );
};

export default Home;

 

다음 container가 전체 화면을 차지하지 않아서 생기는 문제로, screens/Home.js에 flex:1로 일일이 화면 전체를 차지하게 바꿀 수 있습니다. 하지만 모든 screen에 일일이 설정해주어야 하는 문제 가 있습니다. 그것보다는 cardStyle을 사용해 Navigator컴포넌트의 screenOptions에 설정해서 화면 전체에 적용되게 하는 것이 편합니다.

//App.js

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import Home from './screens/Home';
import List from './screens/List';
import Item from './screens/Item';

const Stack = createStackNavigator();

const App = () => {
  return (
    <Stack.Navigator
    screenOptions={{
      cardStyle: {
        flex: 1,  // 모든 화면에 flex: 1을 적용하여 화면 전체 차지
        backgroundColor: '#ffffff',  // 배경색 설정 (선택 사항)
      },
    }}
     initialRouteName="Home"
  >
        <Stack.Screen name="Home" component={Home}/>
        <Stack.Screen name="List" component={List} />
        <Stack.Screen name="Item" component={Item} />
      </Stack.Navigator>
  );
};

export default App;

 

4) 헤더 수정하기

뒤로가기 버튼이나 타이틀을 제공하는 헤더를 수정하는 법을 알아보겠습니다.

-헤더 title 제목 수정하기

screen의 name 속성을 원하는 제목으로 바꾸어주면 상단의 title이 변화합니다. 하지만 이 경우, 상속받는 아래의 모든 컴포넌트의 name도 수정해주어야 한다는 단점이 있습니다.

그래서 Screen의 options로 headerTitle을 줄것입니다.

//App.js

        <Stack.Screen name="Home" component={Home}/>
        <Stack.Screen name="List" component={List} options={{ headerTitle: 'List Screen' }}/>
        <Stack.Screen name="Detail" component={Item} />

 

//List.js

    navigation.navigate('Detail', {id: item._id, name: item.name});

 


-헤더의 스타일 수정하기

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import Home from './screens/Home';
import List from './screens/List';
import Item from './screens/Item';

const Stack = createStackNavigator();

const App = () => {
  return (
    <Stack.Navigator
    screenOptions={{
      cardStyle: {
        flex: 1,  // 모든 화면에 flex: 1을 적용하여 화면 전체 차지
        backgroundColor: '#ffffff',  // 배경색 설정 (선택 사항)
      },
      headerStyle: {
        height: 110,
        backgroundColor: '#95a5a6',
        borderBottomWidth: 5,
        borderBottomColor: '#34495e',
      },
      headerTitleStyle: {
        fontSize: 24,
        color: '#ffffff',
      },
      headerTitleAlign: 'center',
    }}
     initialRouteName="Home"
  >
        <Stack.Screen name="Home" component={Home}        />
        <Stack.Screen name="List" component={List} options={{ headerTitle: 'List Screen' }}/>
        <Stack.Screen name="Detail" component={Item} />
      </Stack.Navigator>
  );
};

export default App;

 

안드로이드에서는 타이틀이 중앙으로 정렬되지 않으므로  headerTitleAlign 속성을 이용해야 합니다.


-타이틀 컴포넌트 변경

headerTitle에 컴포넌트를 반환하는 함수를 지정해서 타이틀에 문자열이 아닌 다른 것(예를 들어 이미지)를 렌더링할 수 있습니다.

headerTitle 속성에서 임시함수를 적용했기 때문에 초기 화면에만 이미지가 적용됩니다.

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { MaterialCommunityIcons } from '@expo/vector-icons'; 
import Home from './screens/Home';
import List from './screens/List';
import Item from './screens/Item';

const Stack = createStackNavigator();

const App = () => {
  return (
    <Stack.Navigator
    screenOptions={{
      cardStyle: {
        flex: 1,  // 모든 화면에 flex: 1을 적용하여 화면 전체 차지
        backgroundColor: '#ffffff',  // 배경색 설정 (선택 사항)
      },
      headerStyle: {
        height: 110,
        backgroundColor: '#95a5a6',
        borderBottomWidth: 5,
        borderBottomColor: '#34495e',
      },
      headerTitleStyle: {
        fontSize: 24,
        color: '#ffffff',
      },
      headerTitleAlign: 'center',
      headerTitle: ({ style }) => (
        <MaterialCommunityIcons
          name="react"
          style={[style, { fontSize: 30, color: '#ffffff' }]}
        />
      ),
    }}
     initialRouteName="Home"
  >
        <Stack.Screen name="Home" component={Home}        />
        <Stack.Screen name="List" component={List} options={{ headerTitle: 'List Screen' }}/>
        <Stack.Screen name="Detail" component={Item} />
      </Stack.Navigator>
  );
};

export default App;

 

# MaterialCommunityIcons는 Material Design 스타일의 아이콘들을 제공하는 아이콘 라이브러리입니다. 이 라이브러리는 React Native에서 아이콘을 사용할 수 있도록 해주며, Expo 프로젝트에서 흔히 사용됩니다.


-뒤로가기 버튼 수정하기

뒤로가기 버튼은 이전화면이 없는 경우 생기지 않습니다. 버튼을 누르는 방법외에도 왼쪽끝에서 오른쪽으로 swipe getures를 통해 이전 화면으로 돌아가는 방법도 있습니다.

안드로이드는 버튼의 타이틀을 보여주지 않고, ios는 이전 화면의 타이틀을 같이 보여줍니다.

headerBackTitle을 이용하면 두 플랫폼의 버튼 타이틀 렌더링 여부를 동일하게 설정할 수 있습니다.

headerBackTitleVisible true,를 하면 안드로이드에서도 버튼 타이틀이 나타난다고 하는데 일단 저는 안되더라고요.

headerBackTitle을 이용하면 기본값 Back이 아닌 다른 값을 지정할 수도 있습니다. 빈 화면으로 만들고 싶다면 headerBackTitle 에 ''를 주는 것이 아니라 headerBackTitleVisible를 false로 해야 합니다.

  • headerBackTitle : 백버튼에 사용되는 string값이다(iOS에서). 기본값은 이전 스크린의 title이거나 Back이다. 만약 이 타이틀 공간이 부족하다면 headerBackTitleVisible을 통해 값을 hide 시킬 수 있다
  • headerBackTitleStyle : headerBackTitle을 꾸미는 프로퍼티이며 fontFamily, fontSize 두가지를 지원한다
  • headerBackImageSource : 헤더 백버튼의 아이콘 이미지를 나타낸다. 기본값으로 ios의 경우 < 모양이고 안드로이드는 <- 이다
        <Stack.Screen name="List" component={List} 
            options={{ headerTitle: 'List Screen',
                headerBackTitleVisible: true, 
                headerBackTitleVisible: 'Prev'
         }}

 

-header 색 수정, 뒤로가기 버튼 수정

 


headerTintColor에 지정된 색은 버튼뿐만 아니라 헤더의 타이틀에도 적용되지만, header TitleStyle혹은 headerBackTitleStyle°] 우선순위가 높으므로 headerTintColor에 설정한 색으로 나타나게 하고 싶다면 다른 스타일과 겹치지 않도록 작성하는 것이 중요합니다.

 

-버튼 컴포넌트 (이미지) 변경

headerBacklmage에 컴포넌트를 반환하는 함수를 전달해서 두 플랫폼이 동일한 이 미지를 렌더링하도록 변경할수 있습니다.

import { Platform } from 'react-native';

//...
<Stack.Screen
        name="List"
        component={List}
        options={{
          headerTitle: 'List Screen',
          headerBackTitleVisible: true,
          headerBackTitle: 'Prev',
          headerTitleStyle: { fontSize: 24 },
          headerTintColor: '#e74c3c',
          headerBackImage: ({ tintColor }) => {
            const style = {
              marginRight: 5,
              marginLeft: Platform.OS === 'ios' ? 11 : 8,
            };
            return (
              <MaterialCommunityIcons
                name="keyboard-backspace"
                size={30}
                color={tintColor}
                style={style}
              />
            );
          },
        }}
      />

 

-헤더 버튼 추가

뒤로 가기 버튼의 이미지가 아니라 헤더의 왼쪽 버튼 전체를 변경하고 싶다면 headerLeft에 컴포넌트를 반환하는 함수를 지정합니다. 이와 동일한 방법으로 headerRight에 컴포넌트를 반환하는 함수를 지정하면 헤더의 오른쪽에 원하는 컴포넌트를 렌더링할 수 있습니다.

import React, { useLayoutEffect } from 'react';
import styled from 'styled-components/native';
import { MaterialCommunityIcons } from '@expo/vector-icons';

const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
`;

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

const Item = ({ navigation, route }) => {
  useLayoutEffect(() => {
    navigation.setOptions({
      headerBackTitleVisible: false,
      headerTintColor: '#ffffff',
      headerLeft: ({ onPress, tintColor }) => {
        return (
          <MaterialCommunityIcons
            name="keyboard-backspace"
            size={30}
            style={{ marginLeft: 11 }}
            color={tintColor}
            onPress={onPress}
          />
        );
      },
      headerRight: ({ tintColor }) => (
        <MaterialCommunityIcons
          name="home-variant"
          size={30}
          style={{ marginRight: 11 }}
          color={tintColor}
          onPress={() => navigation.popToTop()}
        />
      ),
    });
  }, [navigation]);

  return (
    <Container>
      <StyledText>Item</StyledText>
      <StyledText>ID: {route.params.id}</StyledText>
      <StyledText>Name: {route.params.name}</StyledText>
    </Container>
  );
};

export default Item;

 

useLayoutEffect Hook은 useEffect Hook과 달리 컴포넌트가 업데이트된 직후 화면이 렌더링되기 전에 실행됩니다.  이 특징 때문에 화면을 렌더링하기 전에 변경할 부분이 있거나 수치 등을 측정해야 하는 상황에서 많이 사용됩니다.

headerLeft 함수의 파라미터에는 다양한 값들이 전달되는데, 그중 onPress는 뒤로 가기 버튼 기능이 전달됩니다. 화면의 왼쪽 버튼을 변경하면서 전혀 다른 기능을 설정하는 경우에는 필요 없지만, 뒤로 가기 버튼의 기능을 그대로 기용하고 싶은 경우 유용하게 사용할 수 있습니다.
headerRight 함수의 파라미터에는 tintColor만 전달되므로 onPress에 원하는 행동을 정의 해줘야 합니다.

navigation에서 제공하는 다양한 함수 중 popToTop 함수는 현재 쌓여 있 는 모든 화면을 내보내고 첫 화면으로 돌아가는 기능입니다.

 

-헤더 감추기

화면 종류나 프로젝트 기획에 따라 헤더를 감춰야 하는 상황에는 headerMode나 headerShown을 할 수 있습니다.

headerMode는 Navigator 컴포넌트의 속성으로 헤더를 렌더 링하는 방법을 설정하는 속성입 니다.

• float: 헤더가 상단에 유지되며 하나의 헤더를 사용합니다.

• screen: 각 화면마다 헤더를 가지며 화면 변경과 함께 나타나거나 사라집니다.

• none: 헤더가 렌더링되지 않습니다

import { navigate } from 'expo-router/build/global-state/routing';
import React from 'react';
import { Button } from 'react-native';
import styled from 'styled-components/native';

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

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

const Home = ({navigation}) => {
  // 화면 렌더링
  return (
    <Container>
      <StyledText>Home</StyledText>
      <Button title="Go to the List Screen" onPress={() => navigation.navigate('List')} />
    </Container>
  );
};

export default Home;

 

노치 문제 해결, 안드로이드 위에 붙는것은 아래로 임시 방편해결했습니다.

더보기
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;

3. 탭  내비게이션

하단에 존재하는 내비게이션을 간단히 탭바라고 하기도 합니다.

 

탭 내비게이션이 무엇인가 참고자료)

https://brunch.co.kr/@cliche-cliche/16

 

내비게이션과 탭

앱을 사용하다 보면 탭과 내비게이션이 동시에 존재하는 UI가 있습니다. 탭은 보통 내비게이션 특정 메뉴에 귀속되는 경우가 많은데, 시각적 위계 차이를 잘 설정하지 못하면 내비게이션이 두

brunch.co.kr


npm install @react-navigation/bottom-tabs

 

-화면구성

더보기

현재 화면을 확인할 수 있는 텍스트가 나타나는 간단한 컴포넌트를 3개 만들었습니다.
//screens/TabScreens.js

import React from 'react';
import styled from 'styled-components/native';

const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
`;

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

export const Mail = () => {
  return (
    <Container>
      <StyledText>Mail</StyledText>
    </Container>
  );
};

export const Meet = () => {
  return (
    <Container>
      <StyledText>Meet</StyledText>
    </Container>
  );
};

export const Settings = () => {
  return (
    <Container>
      <StyledText>Settings</StyledText>
    </Container>
  );
};

 

//navigations/Tab.js

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { Mail, Meet, Settings } from '../screens/TabScreens';

const Tab = createBottomTabNavigator();

const TabNavigation = () => {
  return (
    <Tab.Navigator initialRouteName="Settings">
      <Tab.Screen name="Mail" component={Mail} />
      <Tab.Screen name="Meet" component={Meet} />
      <Tab.Screen name="Settings" component={Settings} /> 
    </Tab.Navigator>
  );
};

export default TabNavigation;

/App.js

import React from 'react';
import { NavigationContainer } from '@react-navigation/native';
import { createStackNavigator } from '@react-navigation/stack';
import { MaterialCommunityIcons } from '@expo/vector-icons'; 

import { Platform } from 'react-native';
import Home from './screens/Home';
import List from './screens/List';
import Item from './screens/Item';
import TabNavigation from './navigations/Tab';

const Stack = createStackNavigator();



const App = () => {
  return (
    <>
    
       <TabNavigation />
    </>
  );
};

export default App;

 

 

2) 탭 바 수정하기

-버튼 아이콘 설정하기

tabBarlcon에 컴포넌트를 반환하는 함수를 지정하면 버튼의 아이콘이 들어갈 자리에 해당 컴 포넌트를 렌더링합니다. tabBarlcon에 설정된 함수에는 color, size, focused값을 포함한 객체가 파라미터로 전달된다는 특징이 있습니다.

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Mail, Meet, Settings } from '../screens/TabScreens';

const Tab = createBottomTabNavigator();

const TabIcon = ({ name, size, color }) => {
  return <MaterialCommunityIcons name={name} size={size} color={color} />;
};

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

export default TabNavigation;

 

화면을 구성하는 Screen 컴포넌트마다 tabBarlcon에 MaterialCommunitylcons 컴포넌트 를 반환하는 함수를 지정했습니다.

반환되는 컴포넌트의 색과 크기는 tabBarlcon에 지정된 함수의 파라미터로 전달되는 color와 size를 이용해서 설정했습니다.

 

만약 Screen 컴포넌트마다 탭 버튼 아이콘을 지정하지 않고 한곳에서 모든 버튼의 아이콘을 관리하고 싶은 경우 Navigator 컴포넌트의 screenOptions 속성을 사용해서 관리할 수 있습 니다.

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Mail, Meet, Settings } from '../screens/TabScreens';

const Tab = createBottomTabNavigator();

const TabIcon = ({ name, size, color }) => {
  return <MaterialCommunityIcons name={name} size={size} color={color} />;
};

const TabNavigation = () => {
  return (
    <Tab.Navigator
      initialRouteName="Settings"
      screenOptions={({ route }) => ({
        tabBarIcon: (props) => {
          let name = '';
          if (route.name === 'Mail') name = 'email';
          else if (route.name === 'Meet') name = 'video';
          else name = 'settings';
          
          return <TabIcon {...props} name={name} />;
        },
      })}
    >
      <Tab.Screen name="Mail" component={Mail} />
      <Tab.Screen name="Meet" component={Meet} />
      <Tab.Screen name="Settings" component={Settings} />
    </Tab.Navigator>
  );
};

export default TabNavigation;

-라벨 수정하기
버튼 아래 보면 컴포넌트의 name값을 기본값으로 사용하는 라벨이 있습니다 tabBarLabel을 통해 변경할 수 있습니다.

      <Tab.Screen name="Mail" component={Mail} 
      
      options={{
        tabBarLabel:"Inbox"
      }}/>

 

 

라벨을 버튼 아이콘의 아래가 아닌 아이콘 옆으로 변경하고 싶으면 labelPostion의 값을 변경해서 조정할 수 있습니다.

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Mail, Meet, Settings } from '../screens/TabScreens';

const Tab = createBottomTabNavigator();

const TabIcon = ({ name, size, color }) => {
  return <MaterialCommunityIcons name={name} size={size} color={color} />;
};

const TabNavigation = () => {
  return (
    <Tab.Navigator
      initialRouteName="Settings"
      screenOptions={{
        tabBarLabelPosition: 'beside-icon', // 텍스트를 아이콘 옆에 배치
      }}
    >
      <Tab.Screen
        name="Mail"
        component={Mail}
        options={{
          tabBarIcon: props => <TabIcon {...props} name="email" />,
        }}
      />
      <Tab.Screen
        name="Meet"
        component={Meet}
        options={{
          tabBarIcon: props => <TabIcon {...props} name="video" />,
        }}
      />
      <Tab.Screen
        name="Settings"
        component={Settings}
        options={{
          tabBarIcon: props => <TabIcon {...props} name="settings" />,
        }}
      />
    </Tab.Navigator>
  );
};

export default TabNavigation;

 

tabBarOptions는 @react-navigation/bottom-tabs 라이브러리에서 더 이상 사용되지 않으며, screenOptions로 설정을 변경해야 합니다.



tabBarShowLabel:false 옵션을 주면 버튼 없이 아이콘만 나타나게 할 수도 있습니다.

      screenOptions={{
        tabBarLabelPosition: 'beside-icon', // 텍스트를 아이콘 옆에 배치
        tabBarShowLabel:false
      }}

-스타일 수정하기

import React from 'react';
import styled from 'styled-components/native';

const Container = styled.View`
  flex: 1;
  justify-content: center;
  align-items: center;
  background-color: #54b7f9; /* 배경색 변경 */
`;

const StyledText = styled.Text`
  font-size: 30px;
  color: white; /* 텍스트 색상 수정 */
`;

export const Mail = () => {
  return (
    <Container>
      <StyledText>Mail</StyledText>
    </Container>
  );
};

export const Meet = () => {
  return (
    <Container>
      <StyledText>Meet</StyledText>
    </Container>
  );
};

export const Settings = () => {
  return (
    <Container>
      <StyledText>Settings</StyledText>
    </Container>
  );
};

 

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Mail, Meet, Settings } from '../screens/TabScreens';

const Tab = createBottomTabNavigator();

const TabIcon = ({ name, size, color }) => {
  return <MaterialCommunityIcons name={name} size={size} color={color} />;
};

const TabNavigation = () => {
  return (
    <Tab.Navigator
      initialRouteName="Settings"
      screenOptions={{
        tabBarLabelPosition: 'beside-icon', // 아이콘 옆에 텍스트 표시
      }}
      tabBarOptions={{
        showLabel: false, // 텍스트 숨기기
        style: {
          backgroundColor: '#54b7f9', // 탭 바 배경색 설정
          borderTopColor: '#ffffff', // 탭 바 경계선 색상
          borderTopWidth: 2, // 탭 바 경계선 두께
        },
      }}
    >
      <Tab.Screen
        name="Mail"
        component={Mail}
        options={{
          tabBarIcon: props => <TabIcon {...props} name="email" />,
        }}
      />
      <Tab.Screen
        name="Meet"
        component={Meet}
        options={{
          tabBarIcon: props => <TabIcon {...props} name="video" />,
        }}
      />
      <Tab.Screen
        name="Settings"
        component={Settings}
        options={{
          tabBarIcon: props => <TabIcon {...props} name="settings" />,
        }}
      />
    </Tab.Navigator>
  );
};

export default TabNavigation;

 

-색 바꾸기

activeTintColor와 inactiveTintColor는 tabBarOptions에서 사용되던 속성인데, 최신 버전의 @react-navigation/bottom-tabs에서 더 이상 지원되지 않습니다. 대신, 이제는 screenOptions에서 tabBarActiveTintColor와 tabBarInactiveTintColor를 사용해야 합니다.

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Mail, Meet, Settings } from '../screens/TabScreens';

const Tab = createBottomTabNavigator();

const TabIcon = ({ name, size, color }) => {
  return <MaterialCommunityIcons name={name} size={size} color={color} />;
};

const TabNavigation = () => {
  return (
    <Tab.Navigator
      initialRouteName="Settings"
      screenOptions={{
        tabBarLabelPosition: 'beside-icon', // 아이콘 옆에 텍스트 표시
        tabBarActiveTintColor: '#ffffff', // 활성화된 탭의 아이콘 색상
        tabBarInactiveTintColor: '#0B92E9', // 비활성화된 탭의 아이콘 색상
        tabBarStyle: {
          backgroundColor: '#54b7f9', // 탭 바 배경색 설정
          borderTopColor: '#ffffff', // 탭 바 경계선 색상
          borderTopWidth: 2, // 탭 바 경계선 두께
        },
      }}
    >
      <Tab.Screen
        name="Mail"
        component={Mail}
        options={{
          tabBarIcon: props => <TabIcon {...props} name="email" />,
        }}
      />
      <Tab.Screen
        name="Meet"
        component={Meet}
        options={{
          tabBarIcon: props => <TabIcon {...props} name="video" />,
        }}
      />
      <Tab.Screen
        name="Settings"
        component={Settings}
        options={{
          tabBarIcon: props => <TabIcon {...props} name="settings" />,
        }}
      />
    </Tab.Navigator>
  );
};

export default TabNavigation;

버튼의 아이콘을 설정하기 위해 barTablcon에 설정한 함수에는 파라미터로 size, color, focused를 가진 객체가 전달됩니다. 이 값 중 focused는 버튼의 선택된 상태를 나타 내는 값인데, 이 값을 이용하면 버튼의 활성화 상태에 따라 다른 버튼을 렌더링하거나 스타일 을 변경할수 있습니다

 

-버튼 상태에 따른 아이콘 렌더링

이번에는 버튼의 활성화 상태에 따라 다른 아이콘이 렌더링되도록 변경해보겠습니다.

import React from 'react';
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
import { MaterialCommunityIcons } from '@expo/vector-icons';
import { Mail, Meet, Settings } from '../screens/TabScreens';

const Tab = createBottomTabNavigator();

const TabIcon = ({ name, size, color }) => {
  return <MaterialCommunityIcons name={name} size={size} color={color} />;
};

const TabNavigation = () => {
  return (
    <Tab.Navigator
      initialRouteName="Settings"
      screenOptions={{
        tabBarLabelPosition: 'beside-icon', // 아이콘 옆에 텍스트 표시
        tabBarActiveTintColor: '#ffffff', // 활성화된 탭의 아이콘 색상
        tabBarInactiveTintColor: '#cfcfcf', // 비활성화된 탭의 아이콘 색상
        tabBarStyle: {
          backgroundColor: '#54b7f9', // 탭 바 배경색 설정
          borderTopColor: '#ffffff', // 탭 바 경계선 색상
          borderTopWidth: 2, // 탭 바 경계선 두께
        },
      }}
    >
      <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>
  );
};

export default TabNavigation;