返回博客

如何使用 React 和实时汇率 API 构建货币转换器

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

如何使用 React 和实时汇率 API 构建货币转换器

使用 React 构建货币转换器是开发者练习 API 集成、React Hooks 和真实世界状态管理的绝佳项目。在本分步教程中,你将构建一个功能完整的实时货币转换器,它能从货币 API 获取实时汇率、支持 170 多种货币,并优雅地处理错误——所有这些都使用现代 React 模式实现。

无论你是在为现有应用添加多货币支持,还是构建独立的转换工具,本指南都能满足你的需求。如果你是新手,想了解汇率的工作原理,请在开始前查阅我们的汇率解释器

你将构建的内容

完成本教程后,你将拥有一个 React 货币转换器,它能:

  • Finexly API 获取实时汇率
  • 通过下拉菜单支持 170 多种货币
  • 实时转换金额(带防抖处理)
  • 优雅地处理加载状态和 API 错误
  • 显示汇率和最后更新时间戳

项目结构如下:

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

前提条件

开始之前,请确保你具备:

  • 已安装 Node.js 18+
  • React HooksuseStateuseEffect)有基本了解
  • 一个免费的 Finexly API 密钥(大约需要 30 秒获取)

第一步:设置你的 React 项目

我们将使用 Vite 作为构建工具:

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

你的开发服务器将在 http://localhost:5173 启动。

第二步:了解 Finexly API

Finexly API 通过简单的 REST 接口提供 170 多种货币的实时和历史汇率。我们将使用的端点:

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 次请求。

第三步:创建自定义 Hook

创建文件 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 };
}

第四步:构建 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>
  );
}

第五步:环境变量

在项目根目录创建 .env 文件:

VITE_FINEXLY_API_KEY=你的API密钥

.env 添加到你的 .gitignore 中以保护 API 密钥。

第六步:连接应用

更新 src/App.tsx

import CurrencyConverter from "./components/CurrencyConverter";
import "./App.css";

function App() {
  return (
    <main>
      <CurrencyConverter />
      <footer>
        <p>汇率由 <a href="https://finexly.com">Finexly</a> 提供</p>
      </footer>
    </main>
  );
}

export default App;

处理边缘情况

生产就绪的转换器需要处理几种边缘情况:

  • 无效金额: 验证 parseFloat 的结果,防止 NaN
  • 相同货币选择: 如果用户选择相同的源货币和目标货币,添加视觉警告
  • 网络故障: Hook 中的错误状态已经处理 API 故障

使用防抖优化性能

添加一个防抖 hook:

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

进阶功能

基本转换器运行后,以下是高价值的改进:

  • 历史汇率图表: 使用 Finexly 历史 API 显示 30 天汇率趋势
  • 多货币转换: 同时显示前 10 种货币的汇率表
  • 收藏货币: 将用户偏好的货币对保存在 localStorage
  • Next.js 服务端渲染: 将 API 调用移到 Route Handler,避免在浏览器中暴露密钥

常见问题

React 最佳汇率 API 是哪个? 对于 React 应用,你需要支持 CORS 的 REST API。Finexly API 在所有计划上都支持 CORS,并以免费套餐提供超过 170 种货币的覆盖。

汇率多久更新一次? Finexly API 对实时计划每 60 秒更新一次汇率,对标准计划每天更新一次。

我可以在客户端 React 应用中使用货币 API 吗? 可以,如果 API 支持 CORS。但对于生产应用,最佳实践是通过自己的后端代理 API 请求。

如何避免在 React 中暴露 API 密钥? 对于开发,使用 Vite 的 .env 文件,并在变量前加 VITE_ 前缀。对于生产,通过 Next.js API Route 或 serverless 函数路由请求。

如何在两种非美元货币之间转换? Finexly API 在所有计划上都支持任意基础货币,这大大简化了这个问题。直接请求源货币作为基础即可。


准备好为你的 React 应用添加实时汇率了吗?获取你的免费 Finexly API 密钥 — 无需信用卡。每月从 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 →