React Native와 Expo란?

React Native는 Facebook(현 Meta)이 개발한 크로스 플랫폼 모바일 프레임워크입니다. JavaScript와 React를 사용하여 iOS와 Android 앱을 동시에 개발할 수 있습니다.

Expo는 React Native 위에 구축된 프레임워크로, 복잡한 네이티브 설정 없이 빠르게 앱을 개발하고 빌드할 수 있게 해줍니다.

왜 Expo를 선택하는가?

항목React Native CLIExpo
초기 설정Xcode, Android Studio 필수Node.js만 있으면 시작
빌드로컬 빌드 (시간 소요)EAS Build (클라우드)
OTA 업데이트별도 설정 필요내장 지원
네이티브 모듈자유롭게 추가Expo SDK + Dev Client

환경 설정

1. Node.js 설치

Expo는 Node.js 18 이상이 필요합니다.

# Node.js 버전 확인
node --version

# nvm을 사용하는 경우
nvm install 20
nvm use 20

2. Expo CLI 설치 및 프로젝트 생성

# 프로젝트 생성
npx create-expo-app@latest my-app
cd my-app

# 개발 서버 시작
npx expo start

Expo Go 앱을 스마트폰에 설치하고 QR코드를 스캔하면 즉시 앱을 확인할 수 있습니다.

프로젝트 구조

my-app/
├── app/                    # 파일 기반 라우팅
│   ├── _layout.tsx         # 루트 레이아웃
│   ├── index.tsx           # 홈 화면
│   └── (tabs)/             # 탭 네비게이션
│       ├── _layout.tsx
│       ├── index.tsx
│       └── settings.tsx
├── components/             # 재사용 컴포넌트
├── assets/                 # 이미지, 폰트
├── app.json                # Expo 설정
└── package.json

핵심 컴포넌트 만들기

기본 화면 구성

import { View, Text, StyleSheet, TouchableOpacity } from 'react-native';

export default function HomeScreen() {
  return (
    <View style={styles.container}>
      <Text style={styles.title}>오늘의운동</Text>
      <Text style={styles.subtitle}>매일 건강한 습관을 만들어보세요</Text>

      <TouchableOpacity style={styles.button}>
        <Text style={styles.buttonText}>운동 시작하기</Text>
      </TouchableOpacity>
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#fff',
    padding: 20,
  },
  title: {
    fontSize: 32,
    fontWeight: 'bold',
    marginBottom: 8,
  },
  subtitle: {
    fontSize: 16,
    color: '#666',
    marginBottom: 32,
  },
  button: {
    backgroundColor: '#333',
    paddingVertical: 16,
    paddingHorizontal: 32,
    borderRadius: 12,
  },
  buttonText: {
    color: '#fff',
    fontSize: 18,
    fontWeight: '600',
  },
});

FlatList로 리스트 렌더링

대량의 데이터를 효율적으로 렌더링하려면 FlatList를 사용합니다.

import { FlatList, View, Text, StyleSheet } from 'react-native';

const exercises = [
  { id: '1', name: '팔굽혀펴기', sets: 3, reps: 15 },
  { id: '2', name: '스쿼트', sets: 4, reps: 12 },
  { id: '3', name: '플랭크', sets: 3, reps: '60초' },
  { id: '4', name: '버피', sets: 3, reps: 10 },
];

function ExerciseItem({ item }) {
  return (
    <View style={styles.item}>
      <Text style={styles.name}>{item.name}</Text>
      <Text style={styles.detail}>{item.sets}세트 × {item.reps}</Text>
    </View>
  );
}

export default function ExerciseList() {
  return (
    <FlatList
      data={exercises}
      keyExtractor={(item) => item.id}
      renderItem={({ item }) => <ExerciseItem item={item} />}
      ItemSeparatorComponent={() => <View style={styles.separator} />}
    />
  );
}

const styles = StyleSheet.create({
  item: {
    padding: 16,
    flexDirection: 'row',
    justifyContent: 'space-between',
    alignItems: 'center',
  },
  name: { fontSize: 16, fontWeight: '600' },
  detail: { fontSize: 14, color: '#666' },
  separator: { height: 1, backgroundColor: '#e5e5e5' },
});

네비게이션 (Expo Router)

Expo Router는 파일 시스템 기반 라우팅을 제공합니다.

탭 네비게이션 설정

{% raw %}

// app/(tabs)/_layout.tsx
import { Tabs } from 'expo-router';

export default function TabLayout() {
  return (
    <Tabs screenOptions={{
      tabBarActiveTintColor: '#333',
      tabBarStyle: { backgroundColor: '#fff' },
    }}>
      <Tabs.Screen
        name="index"
        options={{ title: '홈', tabBarIcon: () => '🏠' }}
      />
      <Tabs.Screen
        name="workout"
        options={{ title: '운동', tabBarIcon: () => '💪' }}
      />
      <Tabs.Screen
        name="history"
        options={{ title: '기록', tabBarIcon: () => '📊' }}
      />
      <Tabs.Screen
        name="settings"
        options={{ title: '설정', tabBarIcon: () => '⚙️' }}
      />
    </Tabs>
  );
}

{% endraw %}

상태 관리

useState와 useEffect

{% raw %}

import { useState, useEffect } from 'react';
import { View, Text } from 'react-native';

export default function WorkoutTimer() {
  const [seconds, setSeconds] = useState(0);
  const [isRunning, setIsRunning] = useState(false);

  useEffect(() => {
    let interval;
    if (isRunning) {
      interval = setInterval(() => {
        setSeconds(prev => prev + 1);
      }, 1000);
    }
    return () => clearInterval(interval);
  }, [isRunning]);

  const formatTime = (s) => {
    const min = Math.floor(s / 60);
    const sec = s % 60;
    return `${min.toString().padStart(2, '0')}:${sec.toString().padStart(2, '0')}`;
  };

  return (
    <View style={{ alignItems: 'center', padding: 20 }}>
      <Text style={{ fontSize: 48, fontWeight: 'bold' }}>
        {formatTime(seconds)}
      </Text>
    </View>
  );
}

{% endraw %}

EAS Build로 앱 빌드

1. EAS CLI 설치 및 설정

# EAS CLI 설치
npm install -g eas-cli

# Expo 계정 로그인
eas login

# 빌드 설정 초기화
eas build:configure

2. eas.json 설정

{
  "build": {
    "preview": {
      "android": {
        "buildType": "apk"
      }
    },
    "production": {
      "android": {
        "buildType": "app-bundle"
      },
      "ios": {
        "buildType": "archive"
      }
    }
  }
}

3. 빌드 실행

# Android APK 빌드 (테스트용)
eas build --platform android --profile preview

# 프로덕션 빌드
eas build --platform android --profile production

# iOS 빌드 (Apple Developer 계정 필요)
eas build --platform ios --profile production

실전 팁

성능 최적화

  1. 불필요한 리렌더링 방지: React.memouseCallback 활용
  2. 이미지 최적화: expo-image 라이브러리로 캐싱 및 지연 로딩
  3. 리스트 최적화: FlatListgetItemLayout 속성으로 스크롤 성능 향상

자주 사용하는 Expo SDK 패키지

# 로컬 데이터 저장
npx expo install expo-sqlite

# 카메라
npx expo install expo-camera

# 알림
npx expo install expo-notifications

# 광고 (AdMob)
npx expo install expo-ads-admob

결론

React Native + Expo 조합은 모바일 앱 개발의 진입 장벽을 크게 낮춰줍니다. 특히 Expo Router의 파일 기반 라우팅과 EAS Build의 클라우드 빌드 시스템은 개인 개발자에게 매우 유용합니다.

실제로 이 기술 스택을 활용하여 오늘의운동 앱을 개발하고 있으며, SQLite를 활용한 로컬 데이터 관리와 AdMob 광고 연동까지 구현하고 있습니다.