跳到文章正文
05 / 应用

React 与 Wagmi 前端集成

连接钱包、读取余额、发起打赏交易,把链上能力带到页面里。

约 12 分钟阅读
第 5 课:前端集成-React 与 Wagmi
本文为作者已发布教程的站内版本。在 X 查看原文与讨论

学习目标: 连接钱包、读取合约,并发送打赏交易。

上一课

本课目录

  • 1. Wagmi 简介
  • 2. 为什么选择 Wagmi?
  • 3. 安装与配置
  • 4. 开发实战:Web3 打赏箱
  • 5. 组装与测试
  • 6. 常见问题与最佳实践

1. Wagmi 简介

Wagmi 是一个基于 React Hooks 构建的 Web3 前端工具集。如果说 Viem 是与区块链通信的底层引擎(类似于 Web2 的 fetch 或 axios),那么 Wagmi 就是为 React 量身定制的业务层封装(类似于 SWR 或 React Query)。

主要特点

  • 开箱即用:内置数十个常用的 React Hooks(连接钱包、查余额、调用合约等)。
  • 原生缓存:底层由 @tanstack/react-query 驱动,自动处理请求去重、缓存和轮询。
  • 类型安全:深度集成 TypeScript,提供 ABI 级别的强类型推导。
  • 极简架构:告别过去 Web3.js / Ethers.js 时代繁琐的 Provider 状态管理。

2. 为什么选择 Wagmi?

对于 Web2 开发者来说,处理区块链交互最头疼的是状态同步。区块链的数据是异步且需要等待区块确认的。

2.1 核心优势对比

React 与 Wagmi 前端集成 · 配图 1

3. 安装与配置

我们将使用 Vite 快速搭建一个现代化的 React 项目。

3.1 初始化项目

npm create vite@latest web3-tip-jar -- --template react-ts
cd web3-tip-jar
npm install

3.2 安装核心依赖

安装 Wagmi、Viem 以及 React Query:

npm install wagmi viem @tanstack/react-query

3.3 配置全局 Provider

在 src 目录下创建 config.ts,配置支持的区块链网络(这里以以太坊主网和 Sepolia 测试网为例):

// src/config.ts
import { http, createConfig } from 'wagmi'
import { mainnet, sepolia } from 'wagmi/chains'

export const config = createConfig({
  chains: [mainnet, sepolia],
  transports: {
    [mainnet.id]: http(),
    [sepolia.id]: http(),
  },
})

修改 src/main.tsx,用 Provider 包裹你的应用:

// src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { WagmiProvider } from 'wagmi'
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import { config } from './config'
import App from './App'

const queryClient = new QueryClient()

ReactDOM.createRoot(document.getElementById('root')!).render(
  <React.StrictMode>
    <WagmiProvider config={config}>
      <QueryClientProvider client={queryClient}>
        <App />
      </QueryClientProvider>
    </WagmiProvider>
  </React.StrictMode>,
)

4. 开发实战:Web3 打赏箱

我们的项目分为三个核心步骤:连接身份、展示数据、发起打赏。

4.1 第一步:连接用户钱包 (Login)

在 Web3 中,连接钱包就是登录。我们创建一个 WalletConnect.tsx 组件。

// src/components/WalletConnect.tsx
import { useAccount, useConnect, useDisconnect } from 'wagmi'
import { injected } from 'wagmi/connectors'

export function WalletConnect() {
  const { address, isConnected } = useAccount()
  const { connect } = useConnect()
  const { disconnect } = useDisconnect()

  if (isConnected) {
    return (
      <div>
        <p>已连接: {address?.slice(0, 6)}...{address?.slice(-4)}</p>
        <button onClick={() => disconnect()}>断开连接</button>
      </div>
    )
  }

  return (
    <button onClick={() => connect({ connector: injected() })}>
      连接 MetaMask
    </button>
  )
}

4.2 第二步:读取链上数据 (Read)

用户连接后,我们要展示他们的 ETH 余额,并尝试解析他们的 ENS 域名(类似 Web2 的获取 User Profile)。

// src/components/UserProfile.tsx
import { useAccount, useBalance, useEnsName } from 'wagmi'

export function UserProfile() {
  const { address } = useAccount()

  // 读取余额
  const { data: balance } = useBalance({ address })
  // 读取 ENS 域名 (例如 alice.eth)
  const { data: ensName } = useEnsName({ address })

  if (!address) return null

  return (
    <div style={{ padding: '20px', border: '1px solid #ccc', marginTop: '20px' }}>
      <h3>👋 欢迎, {ensName ? ensName : '神秘黑客'}</h3>
      <p>你的钱包余额: {balance?.formatted} {balance?.symbol}</p>
    </div>
  )
}

4.3 第三步:发起打赏交易 (Write / Transaction)

这是最激动人心的一步。我们将调用 useSendTransaction 向指定的地址发送 ETH,并处理交易的等待状态。

// src/components/TipJar.tsx
import { useState } from 'react'
import { useSendTransaction, useWaitForTransactionReceipt } from 'wagmi'
import { parseEther } from 'viem'

export function TipJar() {
  const [amount, setAmount] = useState('0.01')

  // 收款人地址(你可以换成你自己的地址)
  const RECEIVER_ADDRESS = '0xYourAddressHere...'

  const { data: hash, sendTransaction, isPending } = useSendTransaction()

  // 监听交易上链状态
  const { isLoading: isConfirming, isSuccess: isConfirmed } =
    useWaitForTransactionReceipt({ hash })

  const handleTip = () => {
    sendTransaction({
      to: RECEIVER_ADDRESS,
      value: parseEther(amount), // 将 0.01 转换为 Wei 单位
    })
  }

  return (
    <div style={{ marginTop: '20px', padding: '20px', background: '#f5f5f5' }}>
      <h3>☕ 请我喝杯咖啡</h3>
      <input
        type="number"
        value={amount}
        onChange={(e) => setAmount(e.target.value)}
        step="0.01"
      /> ETH

      <button
        onClick={handleTip}
        disabled={isPending || isConfirming}
        style={{ marginLeft: '10px' }}
      >
        {isPending ? '钱包确认中...' : isConfirming ? '区块打包中...' : '打赏'}
      </button>

      {/* 状态反馈 */}
      {hash && <p style={{ fontSize: '12px' }}>交易哈希: {hash}</p>}
      {isConfirmed && <p style={{ color: 'green' }}>🎉 打赏成功!非常感谢!</p>}
    </div>
  )
}

5. 组装与测试

将所有组件组装到 App.tsx 中:

// src/App.tsx
import { WalletConnect } from './components/WalletConnect'
import { UserProfile } from './components/UserProfile'
import { TipJar } from './components/TipJar'
import { useAccount } from 'wagmi'

function App() {
  const { isConnected } = useAccount()

  return (
    <div style={{ maxWidth: '500px', margin: '50px auto', fontFamily: 'sans-serif' }}>
      <h1>💎 Web3 打赏箱</h1>
      <WalletConnect />

      {isConnected && (
        <>
          <UserProfile />
          <TipJar />
        </>
      )}
    </div>
  )
}

export default App

5.1 本地运行与调试

运行 npm run dev。确保你的 MetaMask 切换到了 Sepolia 测试网,并拥有一些测试币(可以从水龙头 Faucet 领取)。你可以尝试自己给自己打赏,观察按钮状态从“钱包确认中”到“区块打包中”再到“打赏成功”的丝滑转变。


6. 常见问题与最佳实践

6.1 大数处理 (BigInt)

Web2 开发者常犯的错误是用普通的 Number 处理金额。以太坊的精度是 18 位,极易导致 JS 精度丢失。

  • 最佳实践:永远使用 Viem 提供的 parseEther (输入时) 和 formatEther (展示时),并在底层逻辑中保持 BigInt 类型。

6.2 错误捕获 (Error Handling)

用户随时可能在钱包弹窗中点击“拒绝”。

  • 最佳实践:在 sendTransaction 的 onError 回调中捕获 UserRejectedRequestError,并给用户友好的 Toast 提示,而不是让控制台报错。

6.3 引入组件库 (RainbowKit)

在真实生产环境中,我们通常不会手写基础的连接按钮。

  • 最佳实践:使用 RainbowKit 或 Web3Modal。它们底层完全基于 Wagmi,但为你提供了极其精美的多钱包适配 UI(支持扫码、移动端跳转等)。

关于作者

,现居湖北武汉,拥有 10 多年经验的全栈开发工程师,分享 AI 编程、Web3 与独立开发实践。

X 原文