Y J S

React Native์— ๋Œ€ํ•ด์„œ

React Native๋ž€?

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

React Native์˜ ํŠน์ง•

  1. ํฌ๋กœ์Šค ํ”Œ๋žซํผ ๊ฐœ๋ฐœ

    • ํ•˜๋‚˜์˜ ์ฝ”๋“œ๋ฒ ์ด์Šค๋กœ iOS์™€ Android ์•ฑ ๋™์‹œ ๊ฐœ๋ฐœ
    • ๋Œ€๋ถ€๋ถ„์˜ ์ฝ”๋“œ๋ฅผ ๊ณต์œ ํ•˜์—ฌ ๊ฐœ๋ฐœ ์‹œ๊ฐ„ ๋‹จ์ถ•
  2. ๋„ค์ดํ‹ฐ๋ธŒ ์„ฑ๋Šฅ

    • JavaScript ๋ธŒ๋ฆฌ์ง€๋ฅผ ํ†ตํ•ด ๋„ค์ดํ‹ฐ๋ธŒ ์ปดํฌ๋„ŒํŠธ์™€ ํ†ต์‹ 
    • ๋„ค์ดํ‹ฐ๋ธŒ UI ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ง์ ‘ ๋ Œ๋”๋ง
  3. Hot Reloading

    • ์ฝ”๋“œ ๋ณ€๊ฒฝ ์‹œ ์ฆ‰์‹œ ๋ฐ˜์˜
    • ์•ฑ์„ ์žฌ์‹œ์ž‘ํ•˜์ง€ ์•Š๊ณ ๋„ ๋ณ€๊ฒฝ์‚ฌํ•ญ ํ™•์ธ ๊ฐ€๋Šฅ
  4. ์ปค๋ฎค๋‹ˆํ‹ฐ์™€ ์ƒํƒœ๊ณ„

    • ํ’๋ถ€ํ•œ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์™€ ํŒจํ‚ค์ง€
    • ํ™œ๋ฐœํ•œ ์ปค๋ฎค๋‹ˆํ‹ฐ ์ง€์›

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>
  );
}

๊ฐœ๋ฐœ ํ™˜๊ฒฝ ์„ค์ •

ํ•„์ˆ˜ ์š”๊ตฌ์‚ฌํ•ญ

  1. Node.js (v14 ์ด์ƒ)
  2. npm ๋˜๋Š” yarn
  3. React Native CLI
  4. ํ”Œ๋žซํผ๋ณ„ ๋„๊ตฌ
    • 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 ํด๋ผ์ด์–ธํŠธ

์ฃผ์˜์‚ฌํ•ญ ๋ฐ ๋ชจ๋ฒ” ์‚ฌ๋ก€

  1. ํ”Œ๋žซํผ๋ณ„ ์ฝ”๋“œ ์ž‘์„ฑ

    import { Platform } from "react-native";
    
    const styles = StyleSheet.create({
      container: {
        paddingTop: Platform.OS === "ios" ? 20 : 0,
      },
    });
    
  2. ์•ˆ์ „ ์˜์—ญ ์ฒ˜๋ฆฌ

    import { SafeAreaView } from "react-native-safe-area-context";
    
    <SafeAreaView style={{ flex: 1 }}>{/* Content */}</SafeAreaView>;
    
  3. ์„ฑ๋Šฅ ๋ชจ๋‹ˆํ„ฐ๋ง

    • React DevTools Profiler ์‚ฌ์šฉ
    • Flipper Performance Monitor ํ™œ์šฉ
  4. ์—๋Ÿฌ ์ฒ˜๋ฆฌ

    import { ErrorBoundary } from "react-error-boundary";
    
    function ErrorFallback({ error }) {
      return <Text>Something went wrong: {error.message}</Text>;
    }
    
    <ErrorBoundary FallbackComponent={ErrorFallback}>
      <App />
    </ErrorBoundary>;
    
  5. ํ…Œ์ŠคํŠธ ์ž‘์„ฑ

    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๋Š” ์›น ๊ฐœ๋ฐœ ์ง€์‹์„ ํ™œ์šฉํ•ด ๋ชจ๋ฐ”์ผ ์•ฑ์„ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ๊ฐ•๋ ฅํ•œ ํ”„๋ ˆ์ž„์›Œํฌ์ด๋‹ค. ํฌ๋กœ์Šค ํ”Œ๋žซํผ ๊ฐœ๋ฐœ์˜ ์ด์ ๊ณผ ํ’๋ถ€ํ•œ ์ƒํƒœ๊ณ„๋ฅผ ํ†ตํ•ด ๋น ๋ฅด๊ฒŒ ๋ชจ๋ฐ”์ผ ์•ฑ์„ ๊ตฌ์ถ•ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๋„ค์ดํ‹ฐ๋ธŒ ๋ชจ๋“ˆ์ด๋‚˜ ๋ณต์žกํ•œ ๊ธฐ๋Šฅ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ์ถ”๊ฐ€์ ์ธ ํ•™์Šต๊ณผ ์„ค์ •์ด ํ•„์š”ํ•˜๋‹ค. ํ”„๋กœ์ ํŠธ์˜ ์š”๊ตฌ์‚ฌํ•ญ๊ณผ ํŒ€์˜ ๊ธฐ์ˆ  ์Šคํƒ์„ ๊ณ ๋ คํ•˜์—ฌ ์ ์ ˆํ•œ ๋„๊ตฌ๋ฅผ ์„ ํƒํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•˜๋‹ค.