근황 토크 및 자유게시판

[React Native] Navigation

scone 2024. 3. 3. 22:04

시나리오

  • Tab1 Screen에서 랜더링된 이벤트 알림 리스트에 대해, 각각의 알림을 클릭하면 이와 관련된 페이지가 열리겠끔 Navigation 구현

 

구성

Screens > Tab1 > Tab1Screen.tsx

  • navigationProp import
  • 구성요소를 클릭할 수 있게 만들어주는 TouchableOpacity
import React from 'react';
import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
import { NavigationProp } from '@react-navigation/native';

 

  • TypeScript에서 사용되는 인터페이스 정의
  • Navigation 동작과 관련된 메서드를 포함하고 있는 객체의 타입인, NavigationProp 정의
interface Tab1ScreenProps {
  navigation: NavigationProp<any>;
}

 

 

  • Tab1Screen이라는 함수형 컴포넌트
  • Navigation 객체로 정의한, Tab1ScreenProps를 매개변수로 받는다.
  • renderItem은 CCTVEvent( 리스트 ) 를 매개변수로 받음, 이후 FlatList 통해 랜더링
  • CCTVEvent의 요소는 TouchableOpacity를 통해 사용자가 항목을 터치할 때 시각적인 피드백 제공 (눌리는 느낌)
  • OnPress 라는 이벤트 핸들러를 통해 터치 시 특정 동작을 수행하게 됨.
  • navigation.navigate 메소드를 통해 CCTVDetailScreen 이라는 다른 화면을 호출하게 됨.
  • 여기서 CCTVDetailScreen은 RootStackNavigator.tsx에 정의되어 있음.
  • eventId와 eventTime 이라는 파라미터로 id와 timestamp에 대한 매개변수를 CCTVDetailScreen 화면에 전달할 수 있음.
export default function Tab1Screen(props: Tab1ScreenProps) {
  const { navigation } = props;
  const renderItem = ({ item }: { item: CCTVEvent }) => (
    <TouchableOpacity 
      style={styles.item} 
      onPress={() => navigation.navigate('CCTVDetailScreen', { eventId: item.id, eventTime: item.timestamp.toLocaleString() })}
    >
      <Text style={styles.title}>CCTV 이벤트 알림</Text>
      <Text style={styles.timestamp}>{item.timestamp.toLocaleString()}</Text>
    </TouchableOpacity>
  );

  return (
    <FlatList
      data={mockCCTVEvents}
      renderItem={renderItem}
      keyExtractor={item => item.id}
      style={{ flex: 1 }}
    />
  );
};

 

Screens > Tab1 > CCTVDetailScreen.tsx

  • RouteProp : 네비게이션 시, 전달된 파라미터의 타입 정의
  • CCTVDetailScreenRouteProp에서 RouteProp으로 화면 전환 시, 넘겨 받은 파라미터에 대해 타입에 대해 정의 해놓은 걸 가져옵니다.
  • export
    • 정의된 변수 함수 등을 다른 파일이나 모듈에서 사용할 수 있게 만듬
    • export로 표시된 항목은 import로 불러올 수 있다
import React from 'react';
import { View, Text } from 'react-native';
import { RouteProp } from '@react-navigation/native';
import { RootStackParamList } from '../../navigation/RootStackNavigator';

type CCTVDetailScreenRouteProp = RouteProp<RootStackParamList, 'CCTVDetailScreen'>;
type CCTVDetailScreenProps = {
    route: CCTVDetailScreenRouteProp;
  };


export default function CCTVDetailScreen(props: CCTVDetailScreenProps) {
    const { route } = props;
    const { eventId, eventTime } = route.params;
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>CCTVDetailScreen</Text>
        <Text>{eventId}</Text>
        <Text>{eventTime}</Text>
      </View>
    );
  };

 

Navigation > RootStackNavigator.tsx

  • React Navigation 라이브러리를 사용하여 앱 내의 여러 화면(스크린) 간의 네비게이션(화면 전환)을 관리하는 구성 파일
  • 사용할 화면과 Navigation 관련 라이브러리들 Import
import * as React from 'react';
import { Image } from 'react-native';
import { createStackNavigator } from '@react-navigation/stack';
import SignInScreen from '../screens/SignIn/SignInScreen';
import SignUpScreen from '../screens/SignUp/SignUpScreen';
import BottomTabNavigator from './BottomTabNavigator';
import Tab1Screen from '../screens/Tab1/Tab1Screen';
import Tab2Screen from '../screens/Tab2/Tab2Screen';
import Tab3Screen from '../screens/Tab3/Tab3Screen';
import CCTVDetailScreen from "../screens/Tab1/CCTVDetailScreen";
import { useNavigation } from '@react-navigation/native';

 

  • RootStackParamList
    • Route를 통해 페이지간 넘겨지는 파라미터를 정의했기 때문에, type에 대해서도 정의해서 넣어줘야해서 만들어주게 되었습니다.
  •  createStackNavigator
    • 앱의 화면이 스택 구조로 관리됩니다.
    • 사용자가 새로운 화면으로 이동하면, 그  화면은 스택의 맨 위에 추가됩니다. (쌓기)
    • 사용자가 뒤로 가기를 하면, 스택의 맨 위 화면이 제거되면서 이전 화면으로 돌아갑니다.
export type RootStackParamList = {
  SignIn: undefined;
  SignUp: undefined;
  Home: undefined;
  Tab1Screen: undefined;
  Tab2Screen: undefined;
  Tab3Screen: undefined;
  CCTVDetailScreen: { eventId: string; eventTime: string };
};

const Stack = createStackNavigator<RootStackParamList>();

export default function RootStackNavigator() {
  const navigation = useNavigation();

 

  • Stack.Navigator
    • 앱의 네비게이션 구조를 정의합니다. 
    • initialRouteName : SignInScreen이 최초로 표시되도록 설정합니다.
    • headerLeft에 이미지와 가디언아이즈 문구를 넣어놓았습니다.
export default function RootStackNavigator() {
  const navigation = useNavigation();

  return (
    <Stack.Navigator initialRouteName="SignIn" 
      screenOptions = {{ 
        headerShown: true,
        headerTitle: '가디언아이즈',
        headerLeft: () => (
          <Image
            source={require('../../assets/Logo.png')}
            style={{ width: 50, height: 50 }}
          />)}}>
    <Stack.Screen name="SignIn" component={SignInScreen} />
    <Stack.Screen name="SignUp" component={SignUpScreen} />
    <Stack.Screen name="Home" component={BottomTabNavigator}/>
    <Stack.Screen name="Tab1Screen" component={Tab1Screen} />
    <Stack.Screen name="Tab2Screen" component={Tab2Screen} />
    <Stack.Screen name="Tab3Screen" component={Tab3Screen} />
    <Stack.Screen name="CCTVDetailScreen" component={CCTVDetailScreen} />
    </Stack.Navigator>
  );
}

 

 

사용한 전체 코드

  • Screens > Tab1 > Tab1Screen.tsx
import React from 'react';
import { View, Text, FlatList, StyleSheet, TouchableOpacity } from 'react-native';
import { NavigationProp } from '@react-navigation/native';

interface CCTVEvent {
  id: string;
  timestamp: Date;
}

interface Tab1ScreenProps {
  navigation: NavigationProp<any>;
}



const mockCCTVEvents: CCTVEvent[] = [
  { id: '1', timestamp: new Date('2024-02-06T17:55:00') },
  { id: '2', timestamp: new Date('2024-02-06T17:55:00') },
  { id: '3', timestamp: new Date('2024-02-06T17:55:00') },
  { id: '4', timestamp: new Date('2024-02-06T17:55:00') },
  { id: '5', timestamp: new Date('2024-02-06T17:55:00') },
  { id: '6', timestamp: new Date('2024-02-06T17:55:00') },
  { id: '7', timestamp: new Date('2024-02-06T17:55:00') },
  { id: '8', timestamp: new Date('2024-02-06T17:55:00') },
  { id: '9', timestamp: new Date('2024-02-06T17:55:00') },
  { id: '10', timestamp: new Date('2024-02-06T17:55:00') },
  { id: '11', timestamp: new Date('2024-02-06T17:55:00') },
  { id: '12', timestamp: new Date('2024-02-06T17:55:00') },
];

export default function Tab1Screen(props: Tab1ScreenProps) {
  const { navigation } = props;
  const renderItem = ({ item }: { item: CCTVEvent }) => (
    <TouchableOpacity 
      style={styles.item} 
      onPress={() => navigation.navigate('CCTVDetailScreen', { eventId: item.id, eventTime: item.timestamp.toLocaleString() })}
    >
      <Text style={styles.title}>CCTV 이벤트 알림</Text>
      <Text style={styles.timestamp}>{item.timestamp.toLocaleString()}</Text>
    </TouchableOpacity>
  );

  return (
    <FlatList
      data={mockCCTVEvents}
      renderItem={renderItem}
      keyExtractor={item => item.id}
      style={{ flex: 1 }}
    />
  );
};

const styles = StyleSheet.create({
  // 기존의 styles 정의...
  item: {
    backgroundColor: '#f9c2ff',
    padding: 20,
    marginVertical: 8,
    marginHorizontal: 16,
  },
  title: {
    fontSize: 32,
  },
  timestamp: {
    fontSize: 16,
  },
});

CCTV 이벤트 알림을 클릭하면 CCTVDetailScreen 화면으로 넘어간다.

 

 

  • Screens > Tab1 > CCTVDetailScreen.tsx
import React from 'react';
import { View, Text } from 'react-native';
import { RouteProp } from '@react-navigation/native';
import { RootStackParamList } from '../../navigation/RootStackNavigator';

type CCTVDetailScreenRouteProp = RouteProp<RootStackParamList, 'CCTVDetailScreen'>;
type CCTVDetailScreenProps = {
    route: CCTVDetailScreenRouteProp;
  };


export default function CCTVDetailScreen(props: CCTVDetailScreenProps) {
    const { route } = props;
    const { eventId, eventTime } = route.params;
    return (
      <View style={{ flex: 1, alignItems: 'center', justifyContent: 'center' }}>
        <Text>CCTVDetailScreen</Text>
        <Text>{eventId}</Text>
        <Text>{eventTime}</Text>
      </View>
    );
  };

로고 이미지와 날짜 시간 항목 가져오는 부분을 좀 수정했어서, 처음에 보여준 이미지와는 조금 다르게 표기되었다.

  • navigation > RootStackNavigator.tsx
// src/navigation/RootStackNavigator.tsx
import * as React from 'react';
import { Image } from 'react-native';
import { createStackNavigator } from '@react-navigation/stack';
import SignInScreen from '../screens/SignIn/SignInScreen';
import SignUpScreen from '../screens/SignUp/SignUpScreen';
import BottomTabNavigator from './BottomTabNavigator';
import Tab1Screen from '../screens/Tab1/Tab1Screen';
import Tab2Screen from '../screens/Tab2/Tab2Screen';
import Tab3Screen from '../screens/Tab3/Tab3Screen';
import CCTVDetailScreen from "../screens/Tab1/CCTVDetailScreen";
import { useNavigation } from '@react-navigation/native';

export type RootStackParamList = {
  SignIn: undefined;
  SignUp: undefined;
  Home: undefined;
  Tab1Screen: undefined;
  Tab2Screen: undefined;
  Tab3Screen: undefined;
  CCTVDetailScreen: { eventId: string; eventTime: string };
};

const Stack = createStackNavigator<RootStackParamList>();

export default function RootStackNavigator() {
  const navigation = useNavigation();

  return (
    <Stack.Navigator initialRouteName="SignIn" 
      screenOptions = {{ 
        headerShown: true,
        headerTitle: '가디언아이즈',
        headerLeft: () => (
          <Image
            source={require('../../assets/Logo.png')}
            style={{ width: 50, height: 50 }}
          />)}}>
    <Stack.Screen name="SignIn" component={SignInScreen} />
    <Stack.Screen name="SignUp" component={SignUpScreen} />
    <Stack.Screen name="Home" component={BottomTabNavigator}/>
    <Stack.Screen name="Tab1Screen" component={Tab1Screen} />
    <Stack.Screen name="Tab2Screen" component={Tab2Screen} />
    <Stack.Screen name="Tab3Screen" component={Tab3Screen} />
    <Stack.Screen name="CCTVDetailScreen" component={CCTVDetailScreen} />
    </Stack.Navigator>
  );
}