근황 토크 및 자유게시판
[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>
);
}