React Native/React Native_study

[리액트 네이티브를 다루는 기술 #8] 5장 리액트 내비게이션으로 여러 화면 관리하기 (p.244 ~ 284)

bocoder
728x90
반응형

@ List

* 5.3 다양한 내비게이터

** 5.3.1 드로어 내비게이터 --> Drawer 라이브러리 에러로 Pass

** 5.3.2 하단 탭 내비게이터

** 5.3.3 머티리얼 상단 탭 내비게이터

** 5.3.4 머티리얼 하단 탭 내비게이터

** 5.3.5 머티리얼 하단 탭 내비게이터 헤더 타이틀 동기화하기

* 5.4 내비게이션 Hooks

** 5.4.1 useNavigation

** 5.4.2 useRoute

** 5.4.3 useFocusEffect

** 5.5 정리

 

@ Note

1. Drawer 라이브러리 에러

 - 라이브러리 설치 시 에러 (해결 완료)

  > reference : https://bocoder.tistory.com/66?category=904878 

ios / android - Drawer Module Install Error in Android

 - const Drawer = createDrawerNavigator(); 설정 시 에러 (해결 필요)

  > reference : https://github.com/software-mansion/react-native-reanimated/issues/1849

ios / android - createDrawerNavigator Error

2. 종류별 Tabs Navigator 의 Props  

 - 하단 탭 내비게이터 : https://reactnavigation.org/docs/bottom-tab-navigator/

 - 머티리얼 상단 탭 내비게이터 : https://reactnavigation.org/docs/material-top-tab-navigator/

 - 머티리얼 하단 탭 내비게이터 : https://reactnavigation.org/docs/material-bottom-tab-navigator/

 - 머티리얼 디자인 관련 : https://callstack.github.io/react-native-p/

 

3. 내비게이션 Hooks

 - useNavigation : Screen으로 사용되고 있지 않은 컴포넌트에서도 navigation 객체 사용 가능

 - useRoute : Screen으로 사용되고 있지 않은 컴포넌트에서도 route 객체 사용 가능

 - useFocusEffect : 화면에 포커스가 작협을 떄 특정 작업을 할 수 있게 하는 Hook

  > useFocusEffect는 반드시 useCallback과 함께 사용해야 함

  > 사용하지 않으면 컴포넌트가 리렌더링 될 때마다 useFocusEffect에 등록한 함수가 호출됨

 

 

@ Result

01234
ios / android - 하단 탭 내비게이터 설정
 
01
ios / android - 머티리얼 상단 탭 내비게이터 설정

 

012345
ios / android - 머티리얼 하단 탭 내비게이터 설정

 

@ Git

 - https://github.com/eunbok-bocoder/LearnReactNavigation.git

 

# Source tree

 

# App.js  ( 하단 탭 내비게이터 )

import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import MainScreen from './screens/MainScreen';
import DetailScreen from './screens/DetailScreen';

const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Main"
          component={MainScreen}
          options={{
            headerShown: false,
          }}
        />
        <Stack.Screen name="Detail" component={DetailScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

 

# screens/MainScreen.js  ( 하단 탭 내비게이터 )

import React from 'react';
import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {Text, Button, View} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';

const Tab = createBottomTabNavigator();

function HomeScreen({navigation}) {
  return (
    <View>
      <Text>Home</Text>
      <Button
        title="Detail 1 열기"
        onPress={() => {
          navigation.push('Detail', {id: 1});
        }}
      />
    </View>
  );
}

function SearchScreen() {
  return (
    <View>
      <Text>Search</Text>
    </View>
  );
}

function NotificationScreen() {
  return (
    <View>
      <Text>Notification</Text>
    </View>
  );
}

function MessageScreen() {
  return (
    <View>
      <Text>Message</Text>
    </View>
  );
}

function MainScreen() {
  return (
    <Tab.Navigator
      initialRouteName="Home"
      screenOptions={{
        tabBarActiveTintColor: '#fb8c00',
        tabBarShowLabel: false,
      }}>
      <Tab.Screen
        name="Home"
        component={HomeScreen}
        options={{
          title: '홈',
          tabBarIcon: ({color, size}) => (
            <Icon name="home" color={color} size={size} />
          ),
        }}
      />
      <Tab.Screen
        name="Search"
        component={SearchScreen}
        options={{
          title: '검색',
          tabBarIcon: ({color, size}) => (
            <Icon name="search" color={color} size={size} />
          ),
        }}
      />
      <Tab.Screen
        name="Notification"
        component={NotificationScreen}
        options={{
          title: '알림',
          tabBarIcon: ({color, size}) => (
            <Icon name="notifications" color={color} size={size} />
          ),
        }}
      />
      <Tab.Screen
        name="Message"
        component={MessageScreen}
        options={{
          title: '메시지',
          tabBarIcon: ({color, size}) => (
            <Icon name="message" color={color} size={size} />
          ),
        }}
      />
    </Tab.Navigator>
  );
}

export default MainScreen;

 

# screens/DetailScreen.js  ( 하단 탭 내비게이터 )

import React, {useEffect} from 'react';
import {View, Text, StyleSheet, Button} from 'react-native';

function DetailScreen({route, navigation}) {
  useEffect(() => {
    navigation.setOptions({
      title: `상세 정보 - ${route.params.id}`,
    });
  }, [navigation, route.params.id]);

  return (
    <View style={styles.block}>
      <Text style={styles.text}>id: {route.params.id}</Text>
      <View style={styles.buttons}>
        <Button
          title="다음"
          // 이전 화면과 동일하면 스택을 쌓지 않고 파라미터만 변경함
          // onPress={() => navigation.navigate('Detail', {id: route.params.id + 1})}
          onPress={() => navigation.push('Detail', {id: route.params.id + 1})}
        />
        <Button title="뒤로가기" onPress={() => navigation.pop()} />
        <Button title="처음으로" onPress={() => navigation.popToTop()} />
      </View>
    </View>
  );
}

export default DetailScreen;

const styles = StyleSheet.create({
  block: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 48,
  },
  buttons: {
    flexDirection: 'row',
  },
});

 

# App.js  ( 머티리얼 상단 탭 내비게이터 )

import React from 'react';
import {NavigationContainer} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import MainScreen from './screens/MainScreen';
import DetailScreen from './screens/DetailScreen';

const Stack = createNativeStackNavigator();

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Main"
          component={MainScreen}
          options={{
            // headerShown: false, // 하단 내비게이터는 기본 헤더 존재
            headerShown: true, // 머티리얼 상단 내비게이터는 기본 헤더가 없음
          }}
        />
        <Stack.Screen name="Detail" component={DetailScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

 

# screens > MainScreen.js  ( 머티리얼 상단 탭 내비게이터 )

import React from 'react';
// import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
import {createMaterialTopTabNavigator} from '@react-navigation/material-top-tabs';
import {Text, Button, View} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';

// 하단 탭 내비게이터
// const Tab = createBottomTabNavigator();

// 머티리얼 상단 탭 내비게이터
const Tab = createMaterialTopTabNavigator();

function HomeScreen({navigation}) {
  return (
    <View>
      <Text>Home</Text>
      <Button
        title="Detail 1 열기"
        onPress={() => {
          navigation.push('Detail', {id: 1});
        }}
      />
    </View>
  );
}

function SearchScreen() {
  return (
    <View>
      <Text>Search</Text>
    </View>
  );
}

function NotificationScreen() {
  return (
    <View>
      <Text>Notification</Text>
    </View>
  );
}

function MessageScreen() {
  return (
    <View>
      <Text>Message</Text>
    </View>
  );
}

function MainScreen() {
  return (
    <Tab.Navigator
      initialRouteName="Home"
      screenOptions={{
        tabBarIndicatorStyle: {
          backgroundColor: '#009688',
        },
        tabBarActiveTintColor: '#009688',
      }}>
      <Tab.Screen
        name="Home"
        component={HomeScreen}
        options={{
          tabBarLabel: '홈',
          tabBarIcon: ({color}) => (
            <Icon name="home" color={color} size={24} />
          ),
        }}
      />
      <Tab.Screen
        name="Search"
        component={SearchScreen}
        options={{
          tabBarLabel: '검색',
          tabBarIcon: ({color}) => (
            <Icon name="search" color={color} size={24} />
          ),
        }}
      />
      <Tab.Screen
        name="Notification"
        component={NotificationScreen}
        options={{
          tabBarLabel: '알림',
          tabBarIcon: ({color}) => (
            <Icon name="notifications" color={color} size={24} />
          ),
        }}
      />
      <Tab.Screen
        name="Message"
        component={MessageScreen}
        options={{
          tabBarLabel: '메시지',
          tabBarIcon: ({color}) => (
            <Icon name="message" color={color} size={24} />
          ),
        }}
      />
    </Tab.Navigator>
  );
}

export default MainScreen;

 

# App.js  ( 머티리얼 하단 탭 내비게이터 )

import React from 'react';
import {
  getFocusedRouteNameFromRoute,
  NavigationContainer,
} from '@react-navigation/native';
import {createNativeStackNavigator} from '@react-navigation/native-stack';
import MainScreen from './screens/MainScreen';
import DetailScreen from './screens/DetailScreen';

const Stack = createNativeStackNavigator();

function getHeaderTitle(route) {
  const routeName = getFocusedRouteNameFromRoute(route) ?? 'Home';
  const nameMap = {
    Home: '홈',
    Search: '검색',
    Notification: '알림',
    Message: '메시지',
  };
  return nameMap[routeName];
}

function App() {
  return (
    <NavigationContainer>
      <Stack.Navigator>
        <Stack.Screen
          name="Main"
          component={MainScreen}
          options={({route}) => ({
            title: getHeaderTitle(route),
          })}
        />
        <Stack.Screen name="Detail" component={DetailScreen} />
      </Stack.Navigator>
    </NavigationContainer>
  );
}

export default App;

 

# screens > MainScreen.js ( 머티리얼 하단 탭 내비게이터 )

import React from 'react';
// import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
// import {createMaterialTopTabNavigator} from '@react-navigation/material-top-tabs';
import {createMaterialBottomTabNavigator} from '@react-navigation/material-bottom-tabs';
import {Text, Button, View} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';

// 하단 탭 내비게이터
// const Tab = createBottomTabNavigator();
// 머티리얼 상단 탭 내비게이터
// const Tab = createMaterialTopTabNavigator();

const Tab = createMaterialBottomTabNavigator();

function HomeScreen({navigation}) {
  return (
    <View>
      <Text>Home</Text>
      <Button
        title="Detail 1 열기"
        onPress={() => {
          navigation.push('Detail', {id: 1});
        }}
      />
    </View>
  );
}

function SearchScreen() {
  return (
    <View>
      <Text>Search</Text>
    </View>
  );
}

function NotificationScreen() {
  return (
    <View>
      <Text>Notification</Text>
    </View>
  );
}

function MessageScreen() {
  return (
    <View>
      <Text>Message</Text>
    </View>
  );
}

function MainScreen() {
  return (
    <Tab.Navigator initialRouteName="Home" labeled={true} shifting={true}>
      <Tab.Screen
        name="Home"
        component={HomeScreen}
        options={{
          tabBarLabel: '홈',
          tabBarIcon: ({color}) => <Icon name="home" color={color} size={24} />,
          tabBarColor: 'black',
          tabBarBadge: 'new',
        }}
      />
      <Tab.Screen
        name="Search"
        component={SearchScreen}
        options={{
          tabBarLabel: '검색',
          tabBarIcon: ({color}) => (
            <Icon name="search" color={color} size={24} />
          ),
          tabBarColor: 'gray',
        }}
      />
      <Tab.Screen
        name="Notification"
        component={NotificationScreen}
        options={{
          tabBarLabel: '알림',
          tabBarIcon: ({color}) => (
            <Icon name="notifications" color={color} size={24} />
          ),
          tabBarColor: 'green',
          tabBarBadge: 30,
        }}
      />
      <Tab.Screen
        name="Message"
        component={MessageScreen}
        options={{
          tabBarLabel: '메시지',
          tabBarIcon: ({color}) => (
            <Icon name="message" color={color} size={24} />
          ),
          tabBarColor: 'blue',
          tabBarBadge: true,
        }}
      />
    </Tab.Navigator>
  );
}

export default MainScreen;

 

# screens > MainScreen.js (내비게이션 Hooks 테스트)

import React, {useEffect, useCallback} from 'react';
// import {createBottomTabNavigator} from '@react-navigation/bottom-tabs';
// import {createMaterialTopTabNavigator} from '@react-navigation/material-top-tabs';
import {createMaterialBottomTabNavigator} from '@react-navigation/material-bottom-tabs';
import {Text, Button, View} from 'react-native';
import Icon from 'react-native-vector-icons/MaterialIcons';
import {useNavigation, useFocusEffect} from '@react-navigation/native';

// 하단 탭 내비게이터
// const Tab = createBottomTabNavigator();
// 머티리얼 상단 탭 내비게이터
// const Tab = createMaterialTopTabNavigator();

const Tab = createMaterialBottomTabNavigator();

// case 1)
// function OpenDetailButton({onPress}) {
//   return <Button title="Detail 1 열기" onPress={onPress} />;
// }

// case 2)
// function OpenDetailButton({navigation}) {
//   return (
//     <Button
//       title="Detail 1 열기"
//       onPress={() => {
//         navigation.push('Detail', {id: 1});
//       }}
//     />
//   );
// }

// case 3)
function OpenDetailButton() {
  const navigation = useNavigation();

  return (
    <Button
      title="Detail 1 열기"
      onPress={() => {
        navigation.push('Detail', {id: 1});
      }}
    />
  );
}

function HomeScreen({navigation}) {
  // useEffect(() => {
  //   console.log('mounted');
  //   return () => {
  //     console.log('unmounted');
  //   };
  // }, []);

  useFocusEffect(
    useCallback(() => {
      console.log('이 화면을 보고 있어요.');
      return () => {
        console.log('다른 화면으로 넘어갔어요.');
      };
    }, []),
  );

  return (
    <View>
      <Text>Home</Text>
      {/* <Button
        title="Detail 1 열기"
        onPress={() => {
          navigation.push('Detail', {id: 1});
        }}
      /> */}
      {/* <OpenDetailButton onPress={() => navigation.push('Detail', {id: 1})} /> */}
      {/* <OpenDetailButton navigation={navigation} /> */}
      <OpenDetailButton />
    </View>
  );
}

function SearchScreen() {
  return (
    <View>
      <Text>Search</Text>
    </View>
  );
}

function NotificationScreen() {
  return (
    <View>
      <Text>Notification</Text>
    </View>
  );
}

function MessageScreen() {
  return (
    <View>
      <Text>Message</Text>
    </View>
  );
}

function MainScreen() {
  return (
    <Tab.Navigator initialRouteName="Home" labeled={true} shifting={true}>
      <Tab.Screen
        name="Home"
        component={HomeScreen}
        options={{
          tabBarLabel: '홈',
          tabBarIcon: ({color}) => <Icon name="home" color={color} size={24} />,
          tabBarColor: 'black',
          tabBarBadge: 'new',
        }}
      />
      <Tab.Screen
        name="Search"
        component={SearchScreen}
        options={{
          tabBarLabel: '검색',
          tabBarIcon: ({color}) => (
            <Icon name="search" color={color} size={24} />
          ),
          tabBarColor: 'gray',
        }}
      />
      <Tab.Screen
        name="Notification"
        component={NotificationScreen}
        options={{
          tabBarLabel: '알림',
          tabBarIcon: ({color}) => (
            <Icon name="notifications" color={color} size={24} />
          ),
          tabBarColor: 'green',
          tabBarBadge: 30,
        }}
      />
      <Tab.Screen
        name="Message"
        component={MessageScreen}
        options={{
          tabBarLabel: '메시지',
          tabBarIcon: ({color}) => (
            <Icon name="message" color={color} size={24} />
          ),
          tabBarColor: 'blue',
          tabBarBadge: true,
        }}
      />
    </Tab.Navigator>
  );
}

export default MainScreen;

 

# screens > DetailScreen.js (내비게이션 Hooks 테스트)

import React, {useEffect} from 'react';
import {View, Text, StyleSheet, Button} from 'react-native';
import {useRoute} from '@react-navigation/native';

function IDText() {
  const route = useRoute();
  return <Text style={styles.text}>id: {route.params.id}</Text>;
}

function DetailScreen({route, navigation}) {
  useEffect(() => {
    navigation.setOptions({
      title: `상세 정보 - ${route.params.id}`,
    });
  }, [navigation, route.params.id]);

  return (
    <View style={styles.block}>
      {/* <Text style={styles.text}>id: {route.params.id}</Text> */}
      <IDText />
      <View style={styles.buttons}>
        <Button
          title="다음"
          // 이전 화면과 동일하면 스택을 쌓지 않고 파라미터만 변경함
          // onPress={() => navigation.navigate('Detail', {id: route.params.id + 1})}
          onPress={() => navigation.push('Detail', {id: route.params.id + 1})}
        />
        <Button title="뒤로가기" onPress={() => navigation.pop()} />
        <Button title="처음으로" onPress={() => navigation.popToTop()} />
      </View>
    </View>
  );
}

export default DetailScreen;

const styles = StyleSheet.create({
  block: {
    flex: 1,
    alignItems: 'center',
    justifyContent: 'center',
  },
  text: {
    fontSize: 48,
  },
  buttons: {
    flexDirection: 'row',
  },
});
728x90
반응형