React Native์ ๋ํด์
React Native๋?
React Native๋ Facebook(ํ Meta)์์ ๊ฐ๋ฐํ ์คํ์์ค ๋ชจ๋ฐ์ผ ์ ํ๋ฆฌ์ผ์ด์ ํ๋ ์์ํฌ์ด๋ค. JavaScript์ React๋ฅผ ์ฌ์ฉํ์ฌ iOS์ Android ์ฑ์ ๋์์ ๊ฐ๋ฐํ ์ ์๊ฒ ํด์ค๋ค.
React Native์ ํน์ง
ํฌ๋ก์ค ํ๋ซํผ ๊ฐ๋ฐ
- ํ๋์ ์ฝ๋๋ฒ ์ด์ค๋ก iOS์ Android ์ฑ ๋์ ๊ฐ๋ฐ
- ๋๋ถ๋ถ์ ์ฝ๋๋ฅผ ๊ณต์ ํ์ฌ ๊ฐ๋ฐ ์๊ฐ ๋จ์ถ
๋ค์ดํฐ๋ธ ์ฑ๋ฅ
- JavaScript ๋ธ๋ฆฌ์ง๋ฅผ ํตํด ๋ค์ดํฐ๋ธ ์ปดํฌ๋ํธ์ ํต์
- ๋ค์ดํฐ๋ธ UI ์ปดํฌ๋ํธ๋ฅผ ์ง์ ๋ ๋๋ง
Hot Reloading
- ์ฝ๋ ๋ณ๊ฒฝ ์ ์ฆ์ ๋ฐ์
- ์ฑ์ ์ฌ์์ํ์ง ์๊ณ ๋ ๋ณ๊ฒฝ์ฌํญ ํ์ธ ๊ฐ๋ฅ
์ปค๋ฎค๋ํฐ์ ์ํ๊ณ
- ํ๋ถํ ๋ผ์ด๋ธ๋ฌ๋ฆฌ์ ํจํค์ง
- ํ๋ฐํ ์ปค๋ฎค๋ํฐ ์ง์
React์ React Native์ ์ฐจ์ด์
์ฃผ์ ์ฐจ์ด์
| ํญ๋ชฉ | React (Web) | React Native |
|---|---|---|
| ๋ ๋๋ง ๋์ | DOM ์์ | ๋ค์ดํฐ๋ธ ์ปดํฌ๋ํธ |
| ์คํ์ผ๋ง | CSS | StyleSheet API |
| ๋ ์ด์์ | CSS Flexbox/Grid | Flexbox๋ง ์ง์ |
| ๋ค๋น๊ฒ์ด์ | React Router | React Navigation ๋ฑ |
| ํ๋ซํผ | ๋ธ๋ผ์ฐ์ | iOS/Android |
์ปดํฌ๋ํธ ์ฐจ์ด
// React (Web)
import React from "react";
function App() {
return (
<div>
<h1>Hello World</h1>
<button onClick={() => alert("Clicked")}>Click me</button>
</div>
);
}
// React Native
import React from "react";
import { View, Text, TouchableOpacity, Alert } from "react-native";
function App() {
return (
<View>
<Text>Hello World</Text>
<TouchableOpacity onPress={() => Alert.alert("Clicked")}>
<Text>Click me</Text>
</TouchableOpacity>
</View>
);
}
๊ฐ๋ฐ ํ๊ฒฝ ์ค์
ํ์ ์๊ตฌ์ฌํญ
- Node.js (v14 ์ด์)
- npm ๋๋ yarn
- React Native CLI
- ํ๋ซํผ๋ณ ๋๊ตฌ
- iOS: Xcode (macOS๋ง)
- Android: Android Studio, JDK
React Native CLI ์ค์น
npm install -g react-native-cli
์ ํ๋ก์ ํธ ์์ฑ
# React Native CLI ์ฌ์ฉ
npx react-native init MyApp
# ๋๋ Expo ์ฌ์ฉ (๊ถ์ฅ)
npx create-expo-app MyApp
Expo vs React Native CLI
Expo
์ฅ์ :
- ๋น ๋ฅธ ์์๊ณผ ๊ฐํธํ ์ค์
- OTA(Over-The-Air) ์ ๋ฐ์ดํธ ์ง์
- ๋ค์ํ ๋ด์ฅ API ์ ๊ณต
- ๊ฐ๋ฐ ๋น๋ ์์ด ๋ฐ๋ก ํ ์คํธ ๊ฐ๋ฅ
๋จ์ :
- ๋ค์ดํฐ๋ธ ๋ชจ๋ ์ถ๊ฐ ์ ์ ํ์
- ์ฑ ํฌ๊ธฐ๊ฐ ์๋์ ์ผ๋ก ํผ
React Native CLI
์ฅ์ :
- ์์ ํ ๋ค์ดํฐ๋ธ ๋ชจ๋ ์ ๊ทผ
- ๋ ์์ ์ฑ ํฌ๊ธฐ
- ๋ ๋ง์ ์ปค์คํฐ๋ง์ด์ง ๊ฐ๋ฅ
๋จ์ :
- ๋ณต์กํ ์ค์ ๊ณผ์
- ๋ค์ดํฐ๋ธ ์ฝ๋ ์์ ํ์ ์ ์ง์ ํ์
๊ธฐ๋ณธ ์ปดํฌ๋ํธ
View
div์ ์ ์ฌํ ์ปจํ
์ด๋ ์ปดํฌ๋ํธ:
import { View } from "react-native";
<View style={{ flex: 1, padding: 20 }}>
<Text>Content</Text>
</View>;
Text
ํ ์คํธ๋ฅผ ํ์ํ๋ ์ปดํฌ๋ํธ:
import { Text } from "react-native";
<Text style={{ fontSize: 18, fontWeight: "bold" }}>Hello React Native</Text>;
Image
์ด๋ฏธ์ง๋ฅผ ํ์ํ๋ ์ปดํฌ๋ํธ:
import { Image } from "react-native";
// ๋ก์ปฌ ์ด๋ฏธ์ง
<Image source={require("./assets/logo.png")} />
// ๋คํธ์ํฌ ์ด๋ฏธ์ง
<Image
source={{ uri: "https://example.com/image.jpg" }}
style={{ width: 200, height: 200 }}
/>
// ๋ฆฌ์ฌ์ด์ฆ ๋ชจ๋
<Image
source={require("./assets/logo.png")}
resizeMode="contain"
/>
ScrollView
์คํฌ๋กค ๊ฐ๋ฅํ ์ปจํ ์ด๋:
import { ScrollView, Text } from "react-native";
<ScrollView>
<Text>Item 1</Text>
<Text>Item 2</Text>
<Text>Item 3</Text>
</ScrollView>;
FlatList
์ฑ๋ฅ ์ต์ ํ๋ ๋ฆฌ์คํธ ์ปดํฌ๋ํธ:
import { FlatList, Text } from "react-native";
const data = [
{ id: "1", title: "Item 1" },
{ id: "2", title: "Item 2" },
{ id: "3", title: "Item 3" },
];
<FlatList
data={data}
keyExtractor={(item) => item.id}
renderItem={({ item }) => <Text>{item.title}</Text>}
/>;
TouchableOpacity / Pressable
ํฐ์น ๊ฐ๋ฅํ ์ปดํฌ๋ํธ:
import { TouchableOpacity, Pressable, Text } from "react-native";
// TouchableOpacity (๊ถ์ฅํ์ง ์์, Pressable ์ฌ์ฉ ๊ถ์ฅ)
<TouchableOpacity onPress={() => console.log("Pressed")}>
<Text>Button</Text>
</TouchableOpacity>
// Pressable (๊ถ์ฅ)
<Pressable
onPress={() => console.log("Pressed")}
onPressIn={() => console.log("Press In")}
onPressOut={() => console.log("Press Out")}
>
<Text>Button</Text>
</Pressable>
์คํ์ผ๋ง
StyleSheet API
import { StyleSheet, View, Text } from "react-native";
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: "#fff",
alignItems: "center",
justifyContent: "center",
},
title: {
fontSize: 24,
fontWeight: "bold",
color: "#333",
},
});
function App() {
return (
<View style={styles.container}>
<Text style={styles.title}>Hello</Text>
</View>
);
}
Flexbox ๋ ์ด์์
const styles = StyleSheet.create({
container: {
flex: 1,
flexDirection: "row", // 'column' (๊ธฐ๋ณธ๊ฐ) ๋๋ 'row'
justifyContent: "center", // ์ฃผ์ถ ์ ๋ ฌ
alignItems: "center", // ๊ต์ฐจ์ถ ์ ๋ ฌ
},
box: {
width: 100,
height: 100,
backgroundColor: "blue",
margin: 10,
},
});
์ฃผ์ ์คํ์ผ ์์ฑ
- ๋ ์ด์์:
flex,flexDirection,justifyContent,alignItems - ํฌ๊ธฐ:
width,height,minWidth,maxHeight - ์ฌ๋ฐฑ:
margin,marginTop,padding,paddingHorizontal - ํ
๋๋ฆฌ:
borderWidth,borderColor,borderRadius - ์์:
backgroundColor,color - ํ
์คํธ:
fontSize,fontWeight,textAlign
์ํ ๊ด๋ฆฌ
useState Hook
import React, { useState } from "react";
import { View, Text, Button } from "react-native";
function Counter() {
const [count, setCount] = useState(0);
return (
<View>
<Text>Count: {count}</Text>
<Button title="Increment" onPress={() => setCount(count + 1)} />
</View>
);
}
Context API
import React, { createContext, useContext, useState } from "react";
const ThemeContext = createContext();
export function ThemeProvider({ children }) {
const [theme, setTheme] = useState("light");
return (
<ThemeContext.Provider value={{ theme, setTheme }}>
{children}
</ThemeContext.Provider>
);
}
export function useTheme() {
return useContext(ThemeContext);
}
Redux / Zustand
# Redux Toolkit
npm install @reduxjs/toolkit react-redux
# Zustand
npm install zustand
๋ค๋น๊ฒ์ด์
React Navigation
๊ฐ์ฅ ๋๋ฆฌ ์ฌ์ฉ๋๋ ๋ค๋น๊ฒ์ด์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ:
npm install @react-navigation/native
npm install @react-navigation/stack
npm install react-native-screens react-native-safe-area-context
import { NavigationContainer } from "@react-navigation/native";
import { createStackNavigator } from "@react-navigation/stack";
const Stack = createStackNavigator();
function App() {
return (
<NavigationContainer>
<Stack.Navigator>
<Stack.Screen name="Home" component={HomeScreen} />
<Stack.Screen name="Details" component={DetailsScreen} />
</Stack.Navigator>
</NavigationContainer>
);
}
function HomeScreen({ navigation }) {
return (
<View>
<Button
title="Go to Details"
onPress={() => navigation.navigate("Details")}
/>
</View>
);
}
ํญ ๋ค๋น๊ฒ์ด์
npm install @react-navigation/bottom-tabs
import { createBottomTabNavigator } from "@react-navigation/bottom-tabs";
const Tab = createBottomTabNavigator();
function MyTabs() {
return (
<Tab.Navigator>
<Tab.Screen name="Home" component={HomeScreen} />
<Tab.Screen name="Settings" component={SettingsScreen} />
</Tab.Navigator>
);
}
๋ค์ดํฐ๋ธ ๋ชจ๋
๋ค์ดํฐ๋ธ ๋ชจ๋ ์์ฑ (Android)
// android/app/src/main/java/com/myapp/ToastModule.java
package com.myapp;
import android.widget.Toast;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
public class ToastModule extends ReactContextBaseJavaModule {
@Override
public String getName() {
return "ToastModule";
}
@ReactMethod
public void show(String message) {
Toast.makeText(getReactApplicationContext(), message, Toast.LENGTH_SHORT).show();
}
}
JavaScript์์ ์ฌ์ฉ
import { NativeModules } from "react-native";
const { ToastModule } = NativeModules;
ToastModule.show("Hello from Native!");
API ํธ์ถ
Fetch API
import { useState, useEffect } from "react";
import { View, Text, FlatList } from "react-native";
function UsersList() {
const [users, setUsers] = useState([]);
const [loading, setLoading] = useState(true);
useEffect(() => {
fetch("https://api.example.com/users")
.then((response) => response.json())
.then((data) => {
setUsers(data);
setLoading(false);
})
.catch((error) => {
console.error(error);
setLoading(false);
});
}, []);
if (loading) {
return <Text>Loading...</Text>;
}
return (
<FlatList
data={users}
keyExtractor={(item) => item.id.toString()}
renderItem={({ item }) => <Text>{item.name}</Text>}
/>
);
}
Axios ์ฌ์ฉ
npm install axios
import axios from "axios";
const fetchUsers = async () => {
try {
const response = await axios.get("https://api.example.com/users");
return response.data;
} catch (error) {
console.error(error);
throw error;
}
};
์ฑ๋ฅ ์ต์ ํ
FlatList ์ต์ ํ
<FlatList
data={items}
keyExtractor={(item) => item.id}
renderItem={renderItem}
removeClippedSubviews={true} // ํ๋ฉด ๋ฐ ํญ๋ชฉ ์ ๊ฑฐ
maxToRenderPerBatch={10} // ํ ๋ฒ์ ๋ ๋๋งํ ํญ๋ชฉ ์
windowSize={10} // ๋ ๋๋งํ ์๋์ฐ ํฌ๊ธฐ
initialNumToRender={10} // ์ด๊ธฐ ๋ ๋๋ง ํญ๋ชฉ ์
/>
๋ฉ๋ชจ์ด์ ์ด์
import React, { memo, useMemo, useCallback } from "react";
const Item = memo(({ item, onPress }) => {
return (
<Pressable onPress={() => onPress(item.id)}>
<Text>{item.title}</Text>
</Pressable>
);
});
function List({ items }) {
const handlePress = useCallback((id) => {
console.log("Pressed:", id);
}, []);
const sortedItems = useMemo(() => {
return [...items].sort((a, b) => a.title.localeCompare(b.title));
}, [items]);
return (
<FlatList
data={sortedItems}
renderItem={({ item }) => <Item item={item} onPress={handlePress} />}
/>
);
}
์ด๋ฏธ์ง ์ต์ ํ
import { Image } from "react-native";
<Image
source={{ uri: imageUrl }}
style={{ width: 200, height: 200 }}
resizeMode="cover"
// ์บ์ฑ ์ต์ ํ
cache="force-cache"
/>;
๋๋ฒ๊น
React Native Debugger
npm install -g react-native-debugger
Flipper
Facebook์์ ๊ฐ๋ฐํ ๋๋ฒ๊น ๋๊ตฌ:
# ์๋์ผ๋ก ์ค์น๋จ (React Native 0.62+)
์ฝ์ ๋ก๊ทธ
import { LogBox } from "react-native";
// ํน์ ๊ฒฝ๊ณ ์จ๊ธฐ๊ธฐ
LogBox.ignoreLogs(["Warning: ..."]);
// ๋ชจ๋ ๊ฒฝ๊ณ ์จ๊ธฐ๊ธฐ (๊ถ์ฅํ์ง ์์)
LogBox.ignoreAllLogs();
๋น๋ ๋ฐ ๋ฐฐํฌ
Android APK ๋น๋
cd android
./gradlew assembleRelease
iOS ๋น๋
cd ios
pod install
# Xcode์์ Archive ์์ฑ
Expo ๋น๋
# Android
eas build --platform android
# iOS
eas build --platform ios
์์ฃผ ์ฌ์ฉํ๋ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
UI ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- React Native Elements: UI ์ปดํฌ๋ํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- NativeBase: ํฌ๋ก์ค ํ๋ซํผ UI ํ๋ ์์ํฌ
- React Native Paper: Material Design ์ปดํฌ๋ํธ
์ํ ๊ด๋ฆฌ
- Redux Toolkit: ์์ธก ๊ฐ๋ฅํ ์ํ ๊ด๋ฆฌ
- Zustand: ๊ฐ๋ฒผ์ด ์ํ ๊ด๋ฆฌ
- MobX: ๋ฐ์ํ ์ํ ๊ด๋ฆฌ
๋ค๋น๊ฒ์ด์
- React Navigation: ๊ฐ์ฅ ์ธ๊ธฐ ์๋ ๋ค๋น๊ฒ์ด์ ๋ผ์ด๋ธ๋ฌ๋ฆฌ
- React Native Navigation: ๋ค์ดํฐ๋ธ ๋ค๋น๊ฒ์ด์
ํผ ๊ด๋ฆฌ
- React Hook Form: ์ฑ๋ฅ ์ต์ ํ๋ ํผ ๊ด๋ฆฌ
- Formik: ํผ ์ํ ๊ด๋ฆฌ
๋คํธ์ํน
- Axios: HTTP ํด๋ผ์ด์ธํธ
- Apollo Client: GraphQL ํด๋ผ์ด์ธํธ
์ฃผ์์ฌํญ ๋ฐ ๋ชจ๋ฒ ์ฌ๋ก
ํ๋ซํผ๋ณ ์ฝ๋ ์์ฑ
import { Platform } from "react-native"; const styles = StyleSheet.create({ container: { paddingTop: Platform.OS === "ios" ? 20 : 0, }, });์์ ์์ญ ์ฒ๋ฆฌ
import { SafeAreaView } from "react-native-safe-area-context"; <SafeAreaView style={{ flex: 1 }}>{/* Content */}</SafeAreaView>;์ฑ๋ฅ ๋ชจ๋ํฐ๋ง
- React DevTools Profiler ์ฌ์ฉ
- Flipper Performance Monitor ํ์ฉ
์๋ฌ ์ฒ๋ฆฌ
import { ErrorBoundary } from "react-error-boundary"; function ErrorFallback({ error }) { return <Text>Something went wrong: {error.message}</Text>; } <ErrorBoundary FallbackComponent={ErrorFallback}> <App /> </ErrorBoundary>;ํ ์คํธ ์์ฑ
npm install --save-dev @testing-library/react-native
React Native vs Flutter vs Native
| ํญ๋ชฉ | React Native | Flutter | Native |
|---|---|---|---|
| ์ธ์ด | JavaScript/TypeScript | Dart | Swift/Kotlin |
| ์ฑ๋ฅ | ์ข์ | ๋งค์ฐ ์ข์ | ์ต๊ณ |
| ๊ฐ๋ฐ ์๋ | ๋น ๋ฆ | ๋น ๋ฆ | ๋๋ฆผ |
| ์ฝ๋ ๊ณต์ | ๋์ | ๋์ | ์์ |
| ์ปค๋ฎค๋ํฐ | ๋งค์ฐ ํผ | ํผ | ํ๋ซํผ๋ณ |
| ๋ค์ดํฐ๋ธ ์ ๊ทผ | ์ ํ์ | ์ ํ์ | ์์ |
๊ฒฐ๋ก
React Native๋ ์น ๊ฐ๋ฐ ์ง์์ ํ์ฉํด ๋ชจ๋ฐ์ผ ์ฑ์ ๊ฐ๋ฐํ ์ ์๊ฒ ํด์ฃผ๋ ๊ฐ๋ ฅํ ํ๋ ์์ํฌ์ด๋ค. ํฌ๋ก์ค ํ๋ซํผ ๊ฐ๋ฐ์ ์ด์ ๊ณผ ํ๋ถํ ์ํ๊ณ๋ฅผ ํตํด ๋น ๋ฅด๊ฒ ๋ชจ๋ฐ์ผ ์ฑ์ ๊ตฌ์ถํ ์ ์๋ค. ํ์ง๋ง ๋ค์ดํฐ๋ธ ๋ชจ๋์ด๋ ๋ณต์กํ ๊ธฐ๋ฅ์ด ํ์ํ ๊ฒฝ์ฐ ์ถ๊ฐ์ ์ธ ํ์ต๊ณผ ์ค์ ์ด ํ์ํ๋ค. ํ๋ก์ ํธ์ ์๊ตฌ์ฌํญ๊ณผ ํ์ ๊ธฐ์ ์คํ์ ๊ณ ๋ คํ์ฌ ์ ์ ํ ๋๊ตฌ๋ฅผ ์ ํํ๋ ๊ฒ์ด ์ค์ํ๋ค.