본문 바로가기
튜토리얼/웹

[React] 실시간 환율 계산기 만들기 (API 연동)

by 루까(Luka) 2026. 2. 27.
반응형

React 실시간 환율 계산기 — API 연동

왜 만들었나

환율 계산기는 API 연동을 처음 배울 때 딱 좋은 예제다. 구조가 단순하고, 실시간 데이터가 오가는 흐름을 눈으로 확인할 수 있다. 이번에는 빌드 도구 없이 React CDN + Babel Standalone으로 index.html 한 파일로 만들었다. npm install 없이 브라우저에서 바로 열면 동작한다.

구현 기능:

  • 실시간 환율 fetch (ExchangeRate-API, 무료 / 인증 없음)
  • 통화 선택 + 금액 입력 → 즉시 환산
  • 통화 스왑 버튼
  • 주요 통화 빠른 비교 패널
  • 로딩 / 에러 / 성공 상태 처리

기술 상세

빌드 없는 React 세팅

<script src="https://unpkg.com/react@18/umd/react.development.js" crossorigin></script>
<script src="https://unpkg.com/react-dom@18/umd/react-dom.development.js" crossorigin></script>
<script src="https://unpkg.com/@babel/standalone/babel.min.js"></script>

<script type="text/babel"> 태그 안에 JSX를 쓰면 Babel이 브라우저에서 실시간 트랜스파일한다. 튜토리얼이나 프로토타입 용도에 적합하다. 프로덕션에는 Vite 기반 빌드를 쓴다.

아키텍처

App
├── useExchangeRates(baseCurrency)   ← 커스텀 훅: API 호출 담당
│   ├── rates: { KRW: 1370, JPY: 150, ... }
│   ├── status: 'loading' | 'live' | 'error'
│   └── updatedAt, refetch
├── fromCurrency / toCurrency        ← 선택된 통화 상태
└── amount                           ← 입력 금액 상태

useExchangeRates가 API 로직을 캡슐화하고, App은 UI와 상태만 관리한다.


핵심 소스 코드

커스텀 훅 — useExchangeRates

function useExchangeRates(baseCurrency) {
  const [rates, setRates]         = useState(null);
  const [status, setStatus]       = useState('loading');
  const [updatedAt, setUpdatedAt] = useState(null);

  const fetchRates = useCallback(async () => {
    setStatus('loading');
    try {
      const res  = await fetch(`https://api.exchangerate-api.com/v4/latest/${baseCurrency}`);
      if (!res.ok) throw new Error(`HTTP ${res.status}`);
      const data = await res.json();
      setRates(data.rates);
      setUpdatedAt(new Date().toLocaleTimeString('ko-KR'));
      setStatus('live');
    } catch (e) {
      setStatus('error');
    }
  }, [baseCurrency]);

  useEffect(() => {
    fetchRates();
  }, [fetchRates]);

  return { rates, status, updatedAt, refetch: fetchRates };
}

useCallback으로 fetchRates를 메모이제이션하고, useEffect의 의존성 배열에 넣는다. baseCurrency가 바뀌면 fetchRates가 새로 생성되고, useEffect가 재실행된다.

환산 계산

const converted = (() => {
  if (!rates || !amount) return null;
  const num = parseFloat(amount.replace(/,/g, ''));
  if (isNaN(num)) return null;
  return num * (rates[toCurrency] ?? 0);
})();

API 응답의 rates 객체는 { KRW: 1370.5, JPY: 149.8, EUR: 0.92, ... } 구조다. 베이스 통화(fromCurrency) 기준 각 통화의 환율이 들어있다. 즉시 실행 함수(IIFE)로 계산 로직을 격리했다.

통화 스왑

function handleSwap() {
  setFromCurrency(toCurrency);
  setToCurrency(fromCurrency);
}

두 상태를 동시에 교환한다. fromCurrency가 바뀌면 useExchangeRates 훅이 새 베이스로 API를 다시 호출한다.

숫자 포맷

function fmt(val, code) {
  if (!val && val !== 0) return '—';
  const locale = code === 'KRW' || code === 'JPY' ? 'ko-KR' : 'en-US';
  const digits = (code === 'KRW' || code === 'JPY') ? 0 : 2;
  return new Intl.NumberFormat(locale, {
    minimumFractionDigits: digits,
    maximumFractionDigits: digits,
  }).format(val);
}

Intl.NumberFormat으로 통화별 소수점 자리수를 다르게 처리한다. 원화/엔화는 소수점 없음, 나머지는 2자리.


기술 선택 비교

항목 이번 선택 대안
빌드 환경 CDN + Babel Standalone Vite, CRA
API ExchangeRate-API (무료/키 없음) Open Exchange Rates (유료), Fixer.io
상태 관리 useState + 커스텀 훅 Redux, Zustand
숫자 포맷 Intl.NumberFormat (브라우저 내장) numeral.js, accounting.js

삽질 기록

1. useCallback 빠뜨려서 무한 루프

// 잘못된 코드
useEffect(() => {
  fetchRates(); // fetchRates가 매 렌더마다 새로 생성 → 무한 루프
}, [fetchRates]);

fetchRatesuseCallback 없이 컴포넌트 안에 선언하면, 렌더마다 새 함수가 생성된다. 이 함수를 useEffect 의존성에 넣으면 루프가 된다. useCallback으로 감싸서 해결.

2. baseCurrency가 바뀌어도 rates가 안 바뀜

useEffect의 의존성 배열을 []로 고정하면 마운트 시 한 번만 실행된다. [fetchRates]로 바꾸고, fetchRatesuseCallback([baseCurrency])로 감싸야 통화 변경 시 재호출된다.

3. 엔화/원화 소수점 표시 문제

149.8 JPY 같은 결과가 나왔다. 원화와 엔화는 소수점 없이 표시해야 자연스럽다. Intl.NumberFormatmaximumFractionDigits 옵션으로 통화별 분기 처리.


마무리

React의 핵심 패턴 세 가지를 이 예제에서 다 볼 수 있다.

  1. useEffect + fetch → 외부 데이터 가져오기
  2. useCallback + 의존성 배열 → 불필요한 재실행 방지
  3. 커스텀 훅 → API 로직 분리

빌드 없이 CDN으로 만들어서 파일 하나로 동작한다는 것도 포인트다. React를 처음 배울 때 프로젝트 셋업 없이 바로 실행해보기 좋다.


기술 스택: React 18, Babel Standalone, ExchangeRate-API, Intl.NumberFormat
소스 코드: GitHub

반응형