Назад к блогу

Как создать конвертер валют с React и API обменных курсов в реальном времени

V
Vlado Grigirov
April 07, 2026
"Currency API" "React" "Tutorial" "JavaScript" "Exchange Rates" "Finexly"

Как создать конвертер валют с React и API обменных курсов в реальном времени

Создание конвертера валют с React — отличный проект для разработчиков, которые хотят практиковать интеграцию API, React-хуки и управление состоянием в реальных приложениях. В этом пошаговом туториале вы создадите полнофункциональный конвертер валют в реальном времени, который получает актуальные обменные курсы из валютного API, поддерживает более 170 валют и изящно обрабатывает ошибки — всё с использованием современных паттернов React.

Если вы новичок и хотите понять, как работают обменные курсы, изучите наш объяснитель обменных курсов перед началом.

Что вы создадите

К концу туториала у вас будет конвертер валют на React, который:

  • Получает обменные курсы в реальном времени из API Finexly
  • Поддерживает более 170 валют через выпадающий список
  • Конвертирует суммы в реальном времени по мере ввода пользователя (с дебаунсингом)
  • Изящно обрабатывает состояния загрузки и ошибки API
  • Отображает курс и временную метку последнего обновления

Структура проекта:

currency-converter/
├── src/
│   ├── components/
│   │   └── CurrencyConverter.tsx
│   ├── hooks/
│   │   └── useExchangeRate.ts
│   ├── App.tsx
│   └── main.tsx
├── package.json
└── vite.config.ts

Предварительные условия

Прежде чем начать, убедитесь, что у вас есть:

  • Установленный Node.js 18+
  • Базовые знания хуков React (useState, useEffect)
  • Бесплатный API-ключ Finexly (получить занимает около 30 секунд)

Шаг 1: Настройка проекта React

В качестве инструмента сборки будем использовать Vite:

npm create vite@latest currency-converter -- --template react-ts
cd currency-converter
npm install
npm run dev

Сервер разработки запустится по адресу http://localhost:5173.

Шаг 2: Понимание API Finexly

API Finexly предоставляет обменные курсы в реальном времени и исторические данные для более чем 170 валют через простой REST-интерфейс:

GET https://finexly.com/api/v1/latest?base=USD

Типичный ответ:

{
  "base": "USD",
  "date": "2026-04-07",
  "rates": {
    "EUR": 0.9182,
    "GBP": 0.7871,
    "JPY": 151.42
  },
  "timestamp": 1744012800
}

Включите свой ключ в заголовок запроса:

Authorization: Bearer ВАШ_API_КЛЮЧ

Вы можете зарегистрироваться для получения бесплатного плана с 1 000 запросов в месяц.

Шаг 3: Создание кастомного хука

Создайте файл src/hooks/useExchangeRate.ts:

import { useState, useEffect, useCallback } from "react";

interface ExchangeRateResult {
  rates: Record<string, number> | null;
  loading: boolean;
  error: string | null;
  lastUpdated: Date | null;
}

const API_KEY = import.meta.env.VITE_FINEXLY_API_KEY;
const BASE_URL = "https://finexly.com/api/v1";

const rateCache: Record<string, { rates: Record<string, number>; timestamp: number }> = {};
const CACHE_TTL_MS = 5 * 60 * 1000;

export function useExchangeRate(baseCurrency: string): ExchangeRateResult {
  const [rates, setRates] = useState<Record<string, number> | null>(null);
  const [loading, setLoading] = useState<boolean>(false);
  const [error, setError] = useState<string | null>(null);
  const [lastUpdated, setLastUpdated] = useState<Date | null>(null);

  const fetchRates = useCallback(async (base: string) => {
    const cached = rateCache[base];
    if (cached && Date.now() - cached.timestamp < CACHE_TTL_MS) {
      setRates(cached.rates);
      setLastUpdated(new Date(cached.timestamp));
      return;
    }

    setLoading(true);
    setError(null);

    try {
      const res = await fetch(`${BASE_URL}/latest?base=${base}`, {
        headers: { Authorization: `Bearer ${API_KEY}` },
      });

      if (!res.ok) throw new Error(`Ошибка API: ${res.status} ${res.statusText}`);

      const data = await res.json();
      rateCache[base] = { rates: data.rates, timestamp: Date.now() };
      setRates(data.rates);
      setLastUpdated(new Date());
    } catch (err) {
      setError(err instanceof Error ? err.message : "Не удалось получить обменные курсы");
    } finally {
      setLoading(false);
    }
  }, []);

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

  return { rates, loading, error, lastUpdated };
}

Шаг 4: Создание компонента CurrencyConverter

Создайте src/components/CurrencyConverter.tsx:

import { useState, useMemo } from "react";
import { useExchangeRate } from "../hooks/useExchangeRate";

const CURRENCIES = [
  "USD", "EUR", "GBP", "JPY", "CAD", "AUD", "CHF", "CNY",
  "INR", "MXN", "BRL", "KRW", "SGD", "HKD", "NOK", "SEK",
];

export default function CurrencyConverter() {
  const [amount, setAmount] = useState<string>("100");
  const [baseCurrency, setBaseCurrency] = useState<string>("USD");
  const [targetCurrency, setTargetCurrency] = useState<string>("EUR");

  const { rates, loading, error, lastUpdated } = useExchangeRate(baseCurrency);

  const convertedAmount = useMemo(() => {
    if (!rates || !amount) return null;
    const numAmount = parseFloat(amount);
    if (isNaN(numAmount) || numAmount < 0) return null;
    const rate = rates[targetCurrency];
    return rate ? (numAmount * rate).toFixed(2) : null;
  }, [rates, amount, targetCurrency]);

  const handleSwap = () => {
    setBaseCurrency(targetCurrency);
    setTargetCurrency(baseCurrency);
  };

  return (
    <div className="converter-card">
      <h2>Конвертер валют в реальном времени</h2>

      <div className="input-row">
        <label htmlFor="amount">Сумма</label>
        <input
          id="amount"
          type="number"
          min="0"
          value={amount}
          onChange={(e) => setAmount(e.target.value)}
          placeholder="Введите сумму"
        />
      </div>

      <div className="currency-row">
        <div className="select-group">
          <label htmlFor="base">Из</label>
          <select id="base" value={baseCurrency} onChange={(e) => setBaseCurrency(e.target.value)}>
            {CURRENCIES.map((c) => <option key={c} value={c}>{c}</option>)}
          </select>
        </div>

        <button className="swap-btn" onClick={handleSwap} aria-label="Поменять валюты местами">⇄</button>

        <div className="select-group">
          <label htmlFor="target">В</label>
          <select id="target" value={targetCurrency} onChange={(e) => setTargetCurrency(e.target.value)}>
            {CURRENCIES.map((c) => <option key={c} value={c}>{c}</option>)}
          </select>
        </div>
      </div>

      <div className="result-area">
        {loading && <p>Получение актуальных курсов...</p>}
        {error && <p className="error-text">⚠ {error}</p>}
        {!loading && !error && convertedAmount !== null && (
          <>
            <p className="converted-value">
              {parseFloat(amount).toLocaleString()} {baseCurrency} =&nbsp;
              <strong>{parseFloat(convertedAmount).toLocaleString()} {targetCurrency}</strong>
            </p>
            {lastUpdated && <p>Курсы обновлены: {lastUpdated.toLocaleTimeString()}</p>}
          </>
        )}
      </div>
    </div>
  );
}

Шаг 5: Переменные окружения

Создайте файл .env в корне проекта:

VITE_FINEXLY_API_KEY=ваш_api_ключ_здесь

Добавьте .env в .gitignore.

Шаг 6: Сборка приложения

Обновите src/App.tsx:

import CurrencyConverter from "./components/CurrencyConverter";

function App() {
  return (
    <main>
      <CurrencyConverter />
      <footer>
        <p>Обменные курсы предоставлены <a href="https://finexly.com">Finexly</a></p>
      </footer>
    </main>
  );
}

export default App;

Оптимизация производительности с дебаунсингом

// src/hooks/useDebounce.ts
import { useState, useEffect } from "react";

export function useDebounce<T>(value: T, delayMs: number): T {
  const [debouncedValue, setDebouncedValue] = useState<T>(value);

  useEffect(() => {
    const timer = setTimeout(() => setDebouncedValue(value), delayMs);
    return () => clearTimeout(timer);
  }, [value, delayMs]);

  return debouncedValue;
}

Дальнейшее развитие

Ценные улучшения включают: исторический график курсов через историческое API Finexly, одновременное конвертирование в несколько валют, сохранение любимых пар в localStorage и рендеринг на стороне сервера с Next.js.

Часто задаваемые вопросы

Какой лучший API обменных курсов для React? Для React-приложений нужен REST API с поддержкой CORS. API Finexly поддерживает CORS на всех планах и охватывает более 170 валют с бесплатным уровнем.

Как часто обновляются обменные курсы? API Finexly обновляет курсы каждые 60 секунд для планов реального времени и раз в день для стандартных планов.

Можно ли использовать валютный API в клиентском React-приложении? Да, если API поддерживает CORS. Для продакшн-приложений рекомендуется маршрутизировать запросы через собственный бэкенд.

Как не раскрыть API-ключ в React? Для разработки используйте файл .env Vite с префиксом VITE_. Для продакшна маршрутизируйте запросы через Next.js API Route или serverless-функцию.

Как конвертировать между двумя валютами без USD? API Finexly поддерживает любую базовую валюту на всех планах, что значительно упрощает эту задачу.


Готовы добавить обменные курсы в реальном времени в своё React-приложение? Получите бесплатный API-ключ Finexly — без кредитной карты. Начните с 1 000 бесплатных запросов в месяц и масштабируйтесь по мере роста.

Vlado Grigirov

Senior Currency Markets Analyst & Financial Strategist

Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...

View full profile →