React Native와 Expo란?

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

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

왜 Expo를 선택하는가?

항목 React Native CLI Expo
초기 설정 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는 파일 시스템 기반 라우팅을 제공합니다.

탭 네비게이션 설정

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

상태 관리

useState와 useEffect

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

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 광고 연동까지 구현하고 있습니다.