개발자의 도구

React Native + Cloudflare

크로스플랫폼 앱과 엣지 백엔드를 한 번에

React Native + Expo 개요

왜 크로스플랫폼인가

  • JavaScript/TypeScript 기반, 하나의 코드로 iOS + Android
  • 네이티브 성능에 근접, 풍부한 생태계
Expo 특징 설명
빠른 시작 네이티브 빌드 환경 없이 바로 개발
Expo Go QR 스캔으로 실기기 테스트
EAS Build 클라우드 빌드 서비스
npx create-expo-app@latest my-app
cd my-app && npx expo start

Expo Router: 파일 기반 라우팅

app/ 디렉토리 구조

app/
├── _layout.tsx      # 루트 레이아웃
├── index.tsx        # 홈 화면 (/)
├── (tabs)/
│   ├── _layout.tsx  # 탭 레이아웃
│   ├── home.tsx     # /home 탭
│   └── profile.tsx  # /profile 탭
└── [id].tsx         # 동적 라우트
  • 파일 이름 = URL 경로
  • _layout.tsx = 공통 레이아웃
  • [param] = 동적 파라미터

핵심 컴포넌트 — 구조와 텍스트

View, Text, ScrollView

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

export default function Home() {
  return (
    <ScrollView>
      <View style={{ padding: 16 }}>
        <Text style={{ fontSize: 24, fontWeight: 'bold' }}>
          안녕하세요!
        </Text>
      </View>
    </ScrollView>
  );
}
  • View = div, Text = span/p, ScrollView = 스크롤 컨테이너

핵심 컴포넌트 — 이미지와 버튼

Image, TouchableOpacity

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

<Image
  source={{ uri: 'https://example.com/photo.jpg' }}
  style={{ width: 200, height: 200, borderRadius: 8 }}
/>

<TouchableOpacity
  onPress={() => alert('눌렀습니다!')}
  style={{ backgroundColor: '#007AFF', padding: 12,
           borderRadius: 8 }}
>
  <Text style={{ color: '#fff', textAlign: 'center' }}>
    버튼 클릭
  </Text>
</TouchableOpacity>

StyleSheet — 컴포넌트 정의

CSS와 비슷하지만 다른 점 (1/2)

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

export default function Card() {
  return (
    <View style={styles.card}>
      <Text style={styles.title}>카드 제목</Text>
      <Text style={styles.description}>카드 설명입니다</Text>
    </View>
  );
}

CSS와의 차이: camelCase, 단위 없음(dp), Flexbox 기본

StyleSheet — 스타일 객체

CSS와 비슷하지만 다른 점 (2/2)

const styles = StyleSheet.create({
  card: {
    backgroundColor: '#fff',
    borderRadius: 12,
    padding: 16,
    marginBottom: 12,
    shadowColor: '#000',       // iOS 그림자
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    elevation: 3,              // Android 그림자
  },
  title: { fontSize: 18, fontWeight: 'bold' },
  description: { fontSize: 14, color: '#666' },
});

상태 관리 — useState + fetch

리액트 훅으로 데이터 다루기 (1/2)

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

export default function ItemList() {
  const [items, setItems] = useState([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    fetch('https://api.example.com/items')
      .then(res => res.json())
      .then(data => { setItems(data); setLoading(false); })
      .catch(err => { console.error(err); setLoading(false); });
  }, []);
  • useState: 컴포넌트 상태 선언
  • useEffect: 마운트 시 API 호출 (빈 배열 = 1회)

상태 관리 — 렌더링

리액트 훅으로 데이터 다루기 (2/2)

  if (loading) return <ActivityIndicator size="large" />;

  return (
    <FlatList
      data={items}
      keyExtractor={(item) => item.id.toString()}
      renderItem={({ item }) => (
        <Text style={{ padding: 12 }}>{item.name}</Text>
      )}
    />
  );
}
  • FlatList: 대량 데이터를 효율적으로 렌더링
  • keyExtractor: 각 항목의 고유 키 지정

Cloudflare Workers 개요

엣지 컴퓨팅의 장점

비교 전통 서버 Workers
위치 특정 리전 전 세계 300+
콜드 스타트 수 초 0ms
스케일링 수동 자동
무료 제한적 일 10만 요청

Workers 생태계

함께 사용할 수 있는 서비스

  • D1: SQLite 기반 데이터베이스
  • KV: 키-값 저장소
  • R2: S3 호환 오브젝트 스토리지
  • Durable Objects: 상태 유지 객체

Wrangler CLI — 설치 & 개발

프로젝트 생성 → 개발 → 배포 (1/2)

설치 및 프로젝트 생성

npm install -g wrangler
wrangler login
wrangler init my-api

로컬 개발 & 배포

wrangler dev          # → localhost:8787
wrangler deploy       # → my-api.workers.dev

Wrangler CLI — 설정 파일

프로젝트 생성 → 개발 → 배포 (2/2)

wrangler.toml 설정

name = "my-api"
main = "src/index.ts"
compatibility_date = "2025-01-01"

[[d1_databases]]
binding = "DB"
database_name = "my-db"
database_id = "xxxxx-xxxxx-xxxxx"

D1 데이터베이스 — 생성

연결 → 쿼리 → 바인딩 (1/2)

데이터베이스 생성

wrangler d1 create my-db

테이블 생성

CREATE TABLE users (
  id INTEGER PRIMARY KEY AUTOINCREMENT,
  name TEXT NOT NULL,
  email TEXT UNIQUE
);

D1 데이터베이스 — Worker에서 사용

연결 → 쿼리 → 바인딩 (2/2)

export default {
  async fetch(request: Request, env: Env) {
    const { results } = await env.DB.prepare(
      "SELECT * FROM users WHERE id = ?"
    ).bind(1).all();

    return Response.json(results);
  },
};

interface Env { DB: D1Database; }

D1은 SQLite 기반 — MySQL과 문법이 다를 수 있습니다

앱에서 API 호출 — 데이터 조회/생성

fetch + Worker URL (1/2)

const API_URL = 'https://my-api.username.workers.dev';

async function getUsers() {
  const res = await fetch(`${API_URL}/users`);
  return res.json();
}

async function createUser(name: string, email: string) {
  const res = await fetch(`${API_URL}/users`, {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ name, email }),
  });
  return res.json();
}

앱에서 API 호출 — 환경 설정

fetch + Worker URL (2/2)

환경별 URL 관리

const API_URL = __DEV__
  ? 'http://localhost:8787'
  : 'https://my-api.workers.dev';
  • 개발 시 wrangler dev로 로컬 서버 실행
  • 프로덕션 배포 후 Workers URL 사용

실전 케이스: MySQL → D1

SQLite 제약사항

MySQL D1 (SQLite) 대응
AUTO_INCREMENT AUTOINCREMENT 키워드 변경
DATETIME TEXT ISO 8601 저장
ENUM 미지원 CHECK 제약
JSON 타입 TEXT 문자열 저장

마이그레이션 주의사항

데이터 불일치 처리

-- 마이그레이션 전 데이터 정제 필수
UPDATE users SET email = 'unknown@example.com'
  WHERE email IS NULL OR email = '';

100% 완벽한 마이그레이션은 없다 — 불일치를 추적하는 체계가 중요

빌드 체크리스트 — 앱

배포 전 확인사항 (1/2)

앱 (React Native / Expo)

  • [ ] npx expo start로 에러 없이 실행되는가
  • [ ] iOS / Android 양쪽에서 테스트했는가
  • [ ] 환경 변수(API URL 등)가 프로덕션 값인가
  • [ ] 불필요한 console.log를 제거했는가
  • [ ] 앱 아이콘과 스플래시 화면이 설정되었는가

빌드 체크리스트 — 백엔드 & 통합

배포 전 확인사항 (2/2)

백엔드 (Cloudflare Workers)

  • [ ] wrangler dev로 로컬 테스트를 완료했는가
  • [ ] D1 마이그레이션이 적용되었는가
  • [ ] CORS 설정이 올바른가
  • [ ] 에러 핸들링이 되어 있는가
  • [ ] wrangler deploy가 성공하는가

통합

  • [ ] 앱 → API 호출이 정상 동작하는가
  • [ ] 네트워크 에러 시 사용자에게 안내되는가

케이스 4 연계: 이 체크리스트를 팀 내 공유하여 활용하세요