โท 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>

ย
ย
โท ์ปดํฌ๋ํธ์ ์ด๋ฒคํธ ์์ฑ
ย
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;
ย
ย