[RN] ๐Ÿ“ฑReact Native ์‚ฌ์šฉํ•˜๊ธฐ

โ–ท React Native

ย 
React Native๋Š” JavaScript์™€ React๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ iOS์™€ Android ๋ชจ๋ฐ”์ผ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๋Š” ์˜คํ”ˆ ์†Œ์Šค ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค.
ย 

๐Ÿ“ฑ Expo๋กœ ์‹œ์ž‘ํ•˜๊ธฐ

# ์ฒ˜์Œ ํ•œ ๋ฒˆ๋งŒ ์„ค์น˜ # npm install -g expo-cli npx create-expo-app --template # ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ๋กœ ํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ # npx create-expo-app -t expo-template-blank-typescript
ย 

๐Ÿ“ฑ CLI๋กœ ์‹œ์ž‘ํ•˜๊ธฐ

ย 

ย 

๐Ÿ“ฑ ๋ชจ๋ฐ”์ผ ์—๋ฎฌ๋ ˆ์ด์…˜

ย 
ย 

ย 

โ–ท Core Components

Core Components โ†’ React Native์˜ ๊ธฐ๋ณธ์ ์ธ UI ์š”์†Œ๋“ค
ย 
SafeAreaView: ํ™”๋ฉด ์ƒ๋‹จ์ด๋‚˜ ํ•˜๋‹จ์˜ ํ™ˆ ๋ฐ” ๋“ฑ๊ณผ ๊ฐ™์€ ์š”์†Œ๋กœ๋ถ€ํ„ฐ ์ปจํ…์ธ ๋ฅผ ์•ˆ์ „ํ•˜๊ฒŒ ๋ฐฐ์น˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋„์™€์คŒ. (OS ๋ฒ„์ „ 11 ์ด์ƒ์ด ์„ค์น˜๋œ iOS ๊ธฐ๊ธฐ์—๋งŒ ์ ์šฉ๋จ)
StatusBar: ๋ชจ๋ฐ”์ผ ๋””๋ฐ”์ด์Šค์˜ ์ƒํƒœ ํ‘œ์‹œ์ค„์— ๋Œ€ํ•œ ์„ค์ •์„ ์ œ์–ด ๊ฐ€๋Šฅ.
<StatusBar backgroundColor="white" barStyle="dark-content" />
ย 
Text: ๊ธ€์ž
<Text numberOfLines={1} ellipsizeMode="tail"> ์ œ๋ชฉ </Text>
ย 
TextIput: ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅ์„ ๋ฐ›์Œ. (๋น„๋ฐ€๋ฒˆํ˜ธ์ผ ๊ฒฝ์šฐ secureTextEntry ์†์„ฑ ์ถ”๊ฐ€ํ•˜๊ธฐ)
FlatList: ๋Œ€๋Ÿ‰์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํšจ์œจ์ ์œผ๋กœ ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•œ ๋ฆฌ์ŠคํŠธ ์ปดํฌ๋„ŒํŠธ์ž„.
/* FlatList ์ปดํฌ๋„ŒํŠธ์˜ ์˜ต์…˜ */ // data => ๋ Œ๋”๋งํ•  ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด์„ ์„ค์ • // renderItem => ๊ฐ ์•„์ดํ…œ์„ ๋ Œ๋”๋งํ•˜๊ธฐ ์œ„ํ•œ ์ฝœ๋ฐฑ ํ•จ์ˆ˜๋ฅผ ์„ค์ • // keyExtractor => ๊ณ ์œ ํ•œ ํ‚ค ๊ฐ’ const MyComponent = () => { // ๊ฐ€์ƒ์˜ ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด const data = [ { id: '1', title: 'Item 1' }, { id: '2', title: 'Item 2' }, { id: '3', title: 'Item 3' }, // ์ถ”๊ฐ€์ ์ธ ์•„์ดํ…œ๋“ค... ]; return ( <FlatList data={data} // ํ‘œ์‹œํ•  ๋ฐ์ดํ„ฐ ๋ฐฐ์—ด renderItem={({ item }) => <ItemComponent {...item} />} // ์ปดํฌ๋„ŒํŠธ๋ฅผ ํ†ตํ•ด ์•„์ดํ…œ์„ ๋ Œ๋”๋ง keyExtractor={(item) => item.id} // ๊ฐ ์•„์ดํ…œ์˜ ๊ณ ์œ  ํ‚ค๋ฅผ ์ง€์ • showsHorizontalScrollIndicator={false} // ๊ฐ€๋กœ ์Šคํฌ๋กค ์ธ๋””์ผ€์ดํ„ฐ ์ˆจ๊น€ horizontal // ๊ฐ€๋กœ ์Šคํฌ๋กค ํ—ˆ์šฉ ItemSeparatorComponent={() => <View style={{ width: 16 }} />} // ์•„์ดํ…œ ์‚ฌ์ด์˜ ๊ฐ„๊ฒฉ contentContainerStyle={{flexGrow: 1, paddingHorizontal: 16}} numColumns={2} // grid์ฒ˜๋Ÿผ ์‚ฌ์šฉ /> ); };
ย 
KeyboardAvoidingView: ํ‚ค๋ณด๋“œ๊ฐ€ ํ™”๋ฉด์„ ๊ฐ€๋ฆฌ๋Š” ์ƒํ™ฉ์—์„œ ์ž๋™์œผ๋กœ ํ™”๋ฉด์„ ์กฐ์ •ํ•˜์—ฌ ์ปดํฌ๋„ŒํŠธ๋ฅผ ๊ฐ€๋ฆฌ์ง€ ์•Š๋„๋ก ๋„์™€์ฃผ๋Š” ์—ญํ• ์„ ํ•จ.
import { KeyboardAvoidingView } from 'react-native'; ... <KeyboardAvoidingView behavior={Platform.OS === 'ios' ? 'padding' : 'height'}> </KeyboardAvoidingView>
ย 
Pressable: ํ„ฐ์น˜ ์ด๋ฒคํŠธ๋ฅผ ๊ฐ์ง€ํ•˜์—ฌ ์‚ฌ์šฉ์ž์˜ ํ„ฐ์น˜ ์ž…๋ ฅ์— ๋ฐ˜์‘ํ•˜๋Š” ์—ญํ• ์„ ํ•จ.
<Pressable onPress={onPressFunction}> <Text>I'm pressable!</Text> </Pressable> ----- <Pressable hitSlop={20} // hitSlop => ์‚ฌ์šฉ์ž๊ฐ€ ํ„ฐ์น˜ ์ด๋ฒคํŠธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ์˜์—ญ์„ ํ™•์žฅํ•˜๋Š” ์—ญํ•  pressRetentionOffset={30} // pressRetentionOffset => ์‚ฌ์šฉ์ž๊ฐ€ ํ„ฐ์น˜๋ฅผ ์‹œ์ž‘ํ•œ ํ›„์—๋„ ํ•ด๋‹น ์˜์—ญ์„ ๋ˆ„๋ฅด๊ณ  ์žˆ๋Š” ๋™์•ˆ์—๋งŒ ํ„ฐ์น˜ ์ด๋ฒคํŠธ๋ฅผ ์œ ์ง€ํ•˜๋Š” ์—ญํ•  > <Text>I'm pressable!</Text> </Pressable>
ย 
TouchableOpacity: ํ„ฐ์น˜ ์ด๋ฒคํŠธ ๋ฐœ์ƒ ์‹œ ํˆฌ๋ช…๋„๊ฐ€ ๋ณ€ํ•จ. (ํ„ฐ์น˜ ํšจ๊ณผ๋ฅผ ์ฃผ๋Š” ๋ฐ ์ฃผ๋กœ ์‚ฌ์šฉ)
ScrollView: ์Šคํฌ๋กค ๊ฐ€๋Šฅ. horizontal ๋ถ™์ด๋ฉด ๊ฐ€๋กœ ์Šคํฌ๋กค ๊ฐ€๋Šฅ
<ScrollView horizontal> ... </ScrollView>
notion image
ย 

ย 

โ–ท ์ปดํฌ๋„ŒํŠธ์˜ ์ด๋ฒคํŠธ ์†์„ฑ

ย 
TextInput ์ปดํฌ๋„ŒํŠธonChangeText: TextInput์— ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•  ๋•Œ๋งˆ๋‹ค ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ onSubmitEditing: TextInput์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ž…๋ ฅํ•˜๊ณ  ์ œ์ถœํ•  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ
ย 
Pressable ์ปดํฌ๋„ŒํŠธonPress: Pressable๋ฅผ ํƒญํ•  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ
ย 
TouchableOpacity ์ปดํฌ๋„ŒํŠธonPress: TouchableOpacity๋ฅผ ํƒญํ•  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ
ย 
ScrollView ์ปดํฌ๋„ŒํŠธonScroll: ScrollView๊ฐ€ ์Šคํฌ๋กค๋˜๋Š” ๋™์•ˆ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ onContentSizeChange: ScrollView์˜ ๋‚ด์šฉ ํฌ๊ธฐ๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ
ย 
FlatList ์ปดํฌ๋„ŒํŠธonEndReached: ์Šคํฌ๋กคํ•˜์—ฌ ๋ฆฌ์ŠคํŠธ์˜ ๋์— ๋„๋‹ฌํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ onRefresh: ๋ฆฌ์ŠคํŠธ๋ฅผ ์ƒˆ๋กœ๊ณ ์นจ ํ•˜๋„๋ก ์š”์ฒญํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ
ย 
Image ์ปดํฌ๋„ŒํŠธonLoad: ์ด๋ฏธ์ง€ ๋กœ๋“œ๊ฐ€ ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ onError: ์ด๋ฏธ์ง€ ๋กœ๋“œ ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์„ ๋•Œ ํ˜ธ์ถœ๋˜๋Š” ์ด๋ฒคํŠธ
ย 

ย 

โ–ท StyleSheet

ย 
// StyleSheet ๊ฐ€์ ธ์˜ค๊ธฐ import { StyleSheet, Text, View } from 'react-native'; const LotsOfStyles = () => { return ( <View style={styles.container}> <Text style={styles.red}>just red</Text> <Text style={styles.bigBlue}>just bigBlue</Text> <Text style={[styles.bigBlue, styles.red]}>bigBlue, then red</Text> <Text style={[styles.red, styles.bigBlue]}>red, then bigBlue</Text> </View> ); }; const styles = StyleSheet.create({ container: { paddingTop: Platform.OS === "android" ? 20 : 0, }, bigBlue: { color: 'blue', fontWeight: 'bold', // ์นด๋ฉœ์ผ€์ด์Šค ์‚ฌ์šฉ fontSize: 30, }, red: { color: 'red', }, }); export default LotsOfStyles;
ย 
โœš ์ฐธ๊ณ 
styled-components๋„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.
# ์„ค์น˜ npm install --save styled-components # ํƒ€์ž…์Šคํฌํฌ๋ฆฝํŠธ ์‚ฌ์šฉ ์‹œ ์ถ”๊ฐ€ ์„ค์น˜ # npm install -D @types/styled-components @types/styled-components-react-native
/* styled-components ์‚ฌ์šฉ ์˜ˆ์‹œ */ import { styled } from 'styled-components'; const StyledTextInput = styled.TextInput` background-color: pink; `; ... <StyledTextInput placeholder="์ด๋ฉ”์ผ" value={email} onChangeText={setEmail}/>
ย 

ย 

โ–ท TailwindCSS

ย 
# ์„ค์น˜ yarn add nativewind yarn add --dev tailwindcss@3.3.2 # tailwind.config.js ํŒŒ์ผ ์ƒ์„ฑ npx tailwindcss init
ย 
tailwind.config.js
module.exports = { content: ['./App.{js,jsx,ts,tsx}', './src/**/*.{js,jsx,ts,tsx}'], theme: { extend: {}, }, plugins: [], };
ย 
babel.config.js
module.exports = { presets: ['module:metro-react-native-babel-preset'], + plugins: ["nativewind/babel"], };
ย 
global.d.ts
/// <reference types="nativewind/types" />
ย 

ย 

โ–ท svg ์‚ฌ์šฉ๋ฒ•

ย 
# ์„ค์น˜ npm install react-native-svg # Expo์ผ ๊ฒฝ์šฐ โ†“ # expo install react-native-svg npm install -D react-native-svg-transformer
ย 
metro.config.js ํŒŒ์ผ ์ƒ์„ฑ
/* metro.config.js */ const { getDefaultConfig } = require("expo/metro-config"); module.exports = (() => { const config = getDefaultConfig(__dirname); const { transformer, resolver } = config; config.transformer = { ...transformer, babelTransformerPath: require.resolve("react-native-svg-transformer"), }; config.resolver = { ...resolver, assetExts: resolver.assetExts.filter((ext) => ext !== "svg"), sourceExts: [...resolver.sourceExts, "svg"], }; return config; })();
ย 

ย 

โ–ท React Navigation

React Navigation โ†’ ๋‹ค๋ฅธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ
ย 
npm install @react-navigation/native @react-navigation/native-stack @react-navigation/bottom-tabs @react-navigation/material-top-tabs --save npm install react-native-screens react-native-safe-area-context # expo์ผ ๊ฒฝ์šฐ # npx expo install react-native-screens react-native-safe-area-context
ย 

๐Ÿ“ฑ NavigationContainer, createNativeStackNavigator

ย 
/* App.js */ // NavigationContainer, createNativeStackNavigator ๊ฐ€์ ธ์˜ค๊ธฐ import { NavigationContainer } from '@react-navigation/native'; import { createNativeStackNavigator } from '@react-navigation/native-stack'; const Stack = createNativeStackNavigator(); // ํƒ€์ž…์Šคํฌ๋ฆฝํŠธ // const navigation = useNavigation<NativeStackNavigationProp<any>>(); const MyStack = () => { return ( <NavigationContainer> // ๊ฐ์‹ธ๊ธฐ <Stack.Navigator> // ๊ฐ์‹ธ๊ธฐ2 <Stack.Screen // ์ปดํฌ๋„ŒํŠธ ๊ฒฝ๋กœ ์ง€์ • name="Home" component={HomeScreen} options={{ headerShown: false }} // ์ƒ๋‹จ์˜ ํƒ€์ดํ‹€ ์•ˆ ๋ณด์ด๊ฒŒ ํ•ด์คŒ // options={{ title: 'Welcome' }} => ์ƒ๋‹จ์˜ ํƒ€์ดํ‹€ ๋ณ€๊ฒฝ /> <Stack.Screen name="Profile" component={ProfileScreen} /> </Stack.Navigator> // ๊ฐ์‹ธ๊ธฐ2 </NavigationContainer> // ๊ฐ์‹ธ๊ธฐ ); };
ย 

๐Ÿ“ฑ Naviagation Props

ย 
function Home({ navigation }) { const goToProfile = () => { navigation.navigate('Profile'); // Profile๋กœ ๊ฒฝ๋กœ ์ด๋™. // navigation.goBack() => ๋’ค๋กœ๊ฐ€๊ธฐ // navigation.push('Profile') => ์ƒˆ ํ™”๋ฉด์„ ์Šคํƒ์— ๋„ฃ์Œ // navigation.replace('Profile') => ํ˜„์žฌ ํ™”๋ฉด์„ ์ƒˆ ํ™”๋ฉด์œผ๋กœ ๊ต์ฒด // navigation.reset({ index: 0, routes: [{ name: 'Profile' }] }) => routes ๋ฐฐ์—ด๋กœ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ฉฐ, index๊ฐ€ 0์ธ Profile๋กœ ์ด๋™ // navigation.setOptions({title: 'Notice'}) => ์ƒ๋‹จ์˜ ํƒ€์ดํ‹€์„ Notice๋กœ ๋ณ€๊ฒฝ }; return ( <Pressable onPress={goToProfile}> <Text>Go to Profile</Text> </Pressable> ); }
ย 

๐Ÿ“ฑ Params

ย 
/* App.tsx */ function App() { const Stack = createNativeStackNavigator(); return ( <NavigationContainer> <Stack.Navigator> <Stack.Screen name="Home" options={{headerShown: false}}> {props => <HomeScreen {...props} paramName="home" />} // ์ด๋Ÿฐ์‹์œผ๋กœ ์‚ฌ์šฉํ•จ </Stack.Screen> <Stack.Screen name="Profile" options={{headerShown: false}}> {props => <ProfileScreen {...props} paramName="profile" />} </Stack.Screen> </Stack.Navigator> </NavigationContainer> ); }
ย 
Params ๋„˜๊ธฐ๊ธฐ
/* Home.jsx */ import {RouteProp, ParamListBase, useNavigation} from '@react-navigation/native'; function Home() { const navigation = useNavigation<NativeStackNavigationProp<any>>(); const goToProfile = () => { navigation.navigate('Profile', { // ๋‘๋ฒˆ์งธ ์ธ์ž๋กœ ์ „๋‹ฌํ•จ id: 1, name: 'tata', }); }; return ( <Pressable onPress={goToProfile}> <Text>Go to Profile</Text> </Pressable> ); }
ย 
Params ์ „๋‹ฌ๋ฐ›๊ธฐ
/* Profile.jsx */ import {useRoute} from '@react-navigation/native'; interface IProfile { id: number; name: string; } interface Props { paramName: string; } function Profile({}: Props) { const route = useRoute<RouteProp<{Profile: IProfile}, 'Profile'>>(); const {params} = route; return ( <View> <Text>Profile</Text> <Text>{params.id}</Text> <Text>{params.name}</Text> </View> ); }
ย 
... + map ์‚ฌ์šฉ
function Home({ navigation }) { const profiles = [ { userId: 1, nickname: 'tata' }, { userId: 2, nickname: 'coco' }, { userId: 3, nickname: 'momo' }, ]; const goToProfile = (userId) => { navigation.navigate('Profile', { userId, }); }; return ( <View> {profiles.map((profile) => ( <Pressable key={profile.userId} onPress={() => goToProfile(profile.userId)}> <Text>{profile.nickname}</Text> </Pressable> ))} </View> ); }
ย 

๐Ÿ“ฑ useNavigation

ย 
import { useNavigation } from '@react-navigation/native'; function Home() { const navigation = useNavigation(); const goToProfile = () => { navigation.navigate('Profile'); // Profile๋กœ ๊ฒฝ๋กœ ์ด๋™. // navigation.goBack() => ๋’ค๋กœ๊ฐ€๊ธฐ // navigation.push('Profile') => ์ƒˆ ํ™”๋ฉด์„ ์Šคํƒ์— ๋„ฃ์Œ // navigation.replace('Profile') => ํ˜„์žฌ ํ™”๋ฉด์„ ์ƒˆ ํ™”๋ฉด์œผ๋กœ ๊ต์ฒด // navigation.reset({ index: 0, routes: [{ name: 'Profile' }] }) => routes ๋ฐฐ์—ด๋กœ ํžˆ์Šคํ† ๋ฆฌ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ฉฐ, index๊ฐ€ 0์ธ Profile๋กœ ์ด๋™ // navigation.setOptions({title: 'Notice'}) => ์ƒ๋‹จ์˜ ํƒ€์ดํ‹€์„ Notice๋กœ ๋ณ€๊ฒฝ }; return ( <Pressable onPress={goToProfile}> <Text>Go to Profile</Text> </Pressable> );
ย 

ย 

โ–ท Alert, Toast

ย 

๐Ÿ“ฑ Alert

try { ... } catch (error) { Alert.alert( '์ด๋ฏธ ์กด์žฌํ•˜๋Š” ํšŒ์›์ž…๋‹ˆ๋‹ค.', error.message, [{ text: '๋‹ซ๊ธฐ', onPress: () => console.log('๋‹ซ๊ธฐ') }], { cancelable: true } // alert์ฐฝ์˜ ๋ฐ–์„ ๋ˆŒ๋Ÿฌ๋„ alert์ฐฝ์„ ๋‹ซ๊ฒŒ ํ•ด์คŒ ); }
ย 

๐Ÿ“ฑ Toast

# ์„ค์น˜ npm install --save react-native-toast-message
ย 
App.js
/* App.js */ import Toast from 'react-native-toast-message'; export function App(props) { return ( <> {/* ... */} <Toast /> </> );
ย 
components/SignupButton.jsx
/* SignupButton.jsx */ import Toast from 'react-native-toast-message'; ... Toast.show({ type: 'success', text1: 'ํšŒ์›๊ฐ€์ž… ์™„๋ฃŒ', text2: `${email}์œผ๋กœ ๊ฐ€์ž…๋˜์—ˆ์Šต๋‹ˆ๋‹ค.`, });
ย 

ย 

โ–ท ๋‹คํฌ๋ชจ๋“œ ์‚ฌ์šฉ

ย 
useColorScheme
import React, { ReactNode } from 'react'; import { SafeAreaView, StatusBar, useColorScheme } from 'react-native'; interface Props { children: ReactNode; } const DefaultLayout = ({children}: Props) => { const theme = useColorScheme(); const isDarkMode = theme === 'dark'; return ( <SafeAreaView> <StatusBar barStyle={isDarkMode ? 'light-content' : 'dark-content'} backgroundColor={isDarkMode ? '#2B2B30' : '#F4F4F4'} /> {children} </SafeAreaView> ); }; export default DefaultLayout;
ย 
ย 

MORE POSTS

you might like

MORE POSTS

you might like

MORE POSTS

you might like

MORE POSTS

you might like