|
| 1 | +--- |
| 2 | +layout: post |
| 3 | +title: 하단 탭 만들기 |
| 4 | +subtitle: React Native 강의 20강 |
| 5 | +tags: [React Native] |
| 6 | +author: Young |
| 7 | +comments: True |
| 8 | +--- |
| 9 | + |
| 10 | +{%raw%} |
| 11 | + |
| 12 | +## 하당 탭 만들기 |
| 13 | + |
| 14 | +React Navigation에서 제공해주는 컴포넌트들... |
| 15 | + |
| 16 | +- BottomTabs : 기본 탭 |
| 17 | +- MaterialBottomTabs : 아래쪽에 탭이 있고 물결 효과가 있음 |
| 18 | +- Material Top Tabs : 위쪽에 탭이 있는 탭 네비게이터 |
| 19 | + |
| 20 | +우리는 BottomTab을 사용할 것임 |
| 21 | + |
| 22 | +```jsx |
| 23 | +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; |
| 24 | +import { ContentRoutes } from './routes'; |
| 25 | +import HomeScreen from '../screens/HomeScreen'; |
| 26 | +import ProfileScreen from '../screens/ProfileScreen'; |
| 27 | +import MapScreen from '../screens/MapScreen'; |
| 28 | +import ListScreen from '../screens/ListScreen'; |
| 29 | + |
| 30 | +const Tab = createBottomTabNavigator(); |
| 31 | + |
| 32 | +const ContentTab = () => { |
| 33 | + return ( |
| 34 | + <Tab.Navigator> |
| 35 | + <Tab.Screen name={ContentRoutes.HOME} component={HomeScreen} /> |
| 36 | + <Tab.Screen name={ContentRoutes.LIST} component={ListScreen} /> |
| 37 | + <Tab.Screen name={ContentRoutes.MAP} component={MapScreen} /> |
| 38 | + <Tab.Screen |
| 39 | + name={ContentRoutes.PROFILE} |
| 40 | + component={ProfileScreen} |
| 41 | + /> |
| 42 | + </Tab.Navigator> |
| 43 | + ); |
| 44 | +}; |
| 45 | + |
| 46 | +export default ContentTab; |
| 47 | +``` |
| 48 | + |
| 49 | +위를 참조해서 만들면 된다. |
| 50 | + |
| 51 | +## option과 icon |
| 52 | + |
| 53 | +```jsx |
| 54 | +<Tab.Navigator |
| 55 | + initialRouteName={ContentRoutes.PROFILE} |
| 56 | + screenOptions={{ |
| 57 | + headerShown: false, |
| 58 | + }} |
| 59 | + > |
| 60 | + |
| 61 | +``` |
| 62 | + |
| 63 | +첫 라우트 지정가능 |
| 64 | +header 보이고 안보이고등의 option지정 가능 |
| 65 | + |
| 66 | +```jsx |
| 67 | +import { createBottomTabNavigator } from '@react-navigation/bottom-tabs'; |
| 68 | +import { ContentRoutes } from './routes'; |
| 69 | +import HomeScreen from '../screens/HomeScreen'; |
| 70 | +import ProfileScreen from '../screens/ProfileScreen'; |
| 71 | +import MapScreen from '../screens/MapScreen'; |
| 72 | +import ListScreen from '../screens/ListScreen'; |
| 73 | +import { MaterialCommunityIcons } from '@expo/vector-icons'; |
| 74 | + |
| 75 | +const Tab = createBottomTabNavigator(); |
| 76 | + |
| 77 | +const getTabBarIcon = ({ focused, color, size, name }) => { |
| 78 | + const iconName = focused ? name : `${name}-outline`; |
| 79 | + return <MaterialCommunityIcons name={iconName} size={size} color={color} />; |
| 80 | +}; |
| 81 | + |
| 82 | +const ContentTab = () => { |
| 83 | + return ( |
| 84 | + <Tab.Navigator |
| 85 | + initialRouteName={ContentRoutes.PROFILE} |
| 86 | + screenOptions={{ |
| 87 | + headerShown: false, |
| 88 | + }} |
| 89 | + > |
| 90 | + <Tab.Screen |
| 91 | + name={ContentRoutes.HOME} |
| 92 | + component={HomeScreen} |
| 93 | + options={{ |
| 94 | + tabBarIcon: props => |
| 95 | + getTabBarIcon({ ...props, name: 'home' }), |
| 96 | + }} |
| 97 | + /> |
| 98 | + <Tab.Screen |
| 99 | + name={ContentRoutes.LIST} |
| 100 | + component={ListScreen} |
| 101 | + options={{ |
| 102 | + tabBarIcon: props => |
| 103 | + getTabBarIcon({ ...props, name: 'post' }), |
| 104 | + }} |
| 105 | + /> |
| 106 | + <Tab.Screen |
| 107 | + name={ContentRoutes.MAP} |
| 108 | + component={MapScreen} |
| 109 | + options={{ |
| 110 | + tabBarIcon: props => |
| 111 | + getTabBarIcon({ ...props, name: 'map' }), |
| 112 | + }} |
| 113 | + /> |
| 114 | + <Tab.Screen |
| 115 | + name={ContentRoutes.PROFILE} |
| 116 | + component={ProfileScreen} |
| 117 | + options={{ |
| 118 | + tabBarIcon: props => |
| 119 | + getTabBarIcon({ ...props, name: 'account' }), |
| 120 | + }} |
| 121 | + /> |
| 122 | + </Tab.Navigator> |
| 123 | + ); |
| 124 | +}; |
| 125 | + |
| 126 | +export default ContentTab; |
| 127 | +``` |
| 128 | + |
| 129 | +- tabBarIcon : 아이콘 넣어주면 됨 |
| 130 | +- tabBarActiveTintColor : 선택된 색상 |
| 131 | +- tabBarInactiveTintColor : 선택 안된 색상 |
| 132 | +- tabBarShowLabel : 라벨 안보이게 하고 싶을 때 false |
| 133 | +- tabBarLabel : 라벨을 커스텀 할 수 있음. |
| 134 | + |
| 135 | +## 작성화면으로 갔을 때, 아래 탭 숨기기 |
| 136 | + |
| 137 | +숨긴다기 보다는, 새로운 화면이 쌓는게 방법임. |
| 138 | + |
| 139 | +글 작성 시 사진 추가 화면과 |
| 140 | +글 작성 화면 |
| 141 | + |
| 142 | +두개를 만들 것임 |
| 143 | + |
| 144 | +```jsx |
| 145 | +<Tab.Screen |
| 146 | + name={'AddButton'} |
| 147 | + component={AddButton} |
| 148 | + options={{ tabBarButton: () => <TabBarAddButton /> }} |
| 149 | +/> |
| 150 | +``` |
| 151 | + |
| 152 | +이렇게 tabBarButton 에다가 연결해두면 아예 버튼을 새로 만들 수 있고 |
| 153 | + |
| 154 | +이 버튼에서 새페이지 열리도록 만들었음. |
| 155 | + |
| 156 | +## updateProfile |
| 157 | + |
| 158 | +1. 일단 로그아웃 시 firebase에서도 로그아웃될 수 있도록 한다. |
| 159 | + |
| 160 | +```jsx |
| 161 | +export const signOut = async () => { |
| 162 | + await signOutFirebase(getAuth()); |
| 163 | +}; |
| 164 | +``` |
| 165 | + |
| 166 | +위와 같이 signOut 함수를 이용하여 해결 |
| 167 | + |
| 168 | +2. profile 업데이트 해준다. |
| 169 | + |
| 170 | +```jsx |
| 171 | +const updateUserInfo = async userInfo => { |
| 172 | + try { |
| 173 | + await updateProfile(getAuth().currentUser, userInfo); |
| 174 | + } catch (error) { |
| 175 | + throw new Error('사용자 정보 수정에 실패했습니다.'); |
| 176 | + } |
| 177 | +}; |
| 178 | +``` |
| 179 | + |
| 180 | +## FastImage |
| 181 | + |
| 182 | +이미지 로컬파일시스템에 캐싱해서 사용하기 |
| 183 | + |
| 184 | +```jsx |
| 185 | +import { Image, StyleSheet, Text, View } from 'react-native'; |
| 186 | +import PropTypes from 'prop-types'; |
| 187 | +import { useEffect, useState } from 'react'; |
| 188 | +import * as Crypto from 'expo-crypto'; |
| 189 | +import * as FileSystem from 'expo-file-system'; |
| 190 | + |
| 191 | +const FastImage = ({ source, ...props }) => { |
| 192 | + const [uri, setUri] = useState(source.uri); |
| 193 | + |
| 194 | + useEffect(() => { |
| 195 | + (async () => { |
| 196 | + try { |
| 197 | + const hashed = await Crypto.digestStringAsync( |
| 198 | + Crypto.CryptoDigestAlgorithm.SHA256, |
| 199 | + source.uri, |
| 200 | + ); |
| 201 | + const fileSystemUri = `${FileSystem.cacheDirectory}${hashed}`; |
| 202 | + |
| 203 | + const metadata = await FileSystem.getInfoAsync(fileSystemUri); |
| 204 | + if (!metadata.exists) { |
| 205 | + await FileSystem.downloadAsync(source.uri, fileSystemUri); |
| 206 | + } |
| 207 | + setUri(fileSystemUri); |
| 208 | + } catch (error) { |
| 209 | + setUri(source.uri); |
| 210 | + } |
| 211 | + })(); |
| 212 | + }, [source.uri]); |
| 213 | + |
| 214 | + return <Image source={{ uri }} {...props} />; |
| 215 | +}; |
| 216 | + |
| 217 | +FastImage.propTypes = { |
| 218 | + source: PropTypes.object.isRequired, |
| 219 | +}; |
| 220 | + |
| 221 | +export default FastImage; |
| 222 | +``` |
| 223 | + |
| 224 | +{%endraw%} |
0 commit comments