Post

Web3-React 构建以太坊 DApp 的 React 框架

Web3-React 构建以太坊 DApp 的 React 框架

Web3-React

Web3-React 是由 Uniswap 团队开发的一个简洁、可扩展的 React 框架,专门用于构建现代以太坊去中心化应用(DApp)。

特性

1. 广泛的钱包支持

Web3-React 支持几乎所有主流的 Web3 钱包和连接方式:

  • 浏览器钱包: MetaMask、Trust Wallet、Tokenary
  • 硬件钱包: Trezor、Ledger
  • 基础设施提供商: Infura、QuickNode
  • 钱包连接协议: WalletConnect

2. 模块化架构

框架采用连接器(Connector)模式,每个钱包类型都有对应的连接器。这种设计使得添加新的钱包类型变得非常简单,同时保持了代码的整洁性。

3. TypeScript 原生支持

Web3-React 从底层就支持 TypeScript,提供了完整的类型定义,让开发过程更加安全和高效。

快速上手

安装

1
npm install @web3-react/core @web3-react/injected-connector @web3-react/walletconnect-connector

基础配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
import { Web3ReactProvider } from "@web3-react/core";
import { Web3Provider } from "@ethersproject/providers";

function getLibrary(provider) {
  const library = new Web3Provider(provider);
  library.pollingInterval = 12000;
  return library;
}

function MyApp({ Component, pageProps }) {
  return (
    <Web3ReactProvider getLibrary={getLibrary}>
      <Component {...pageProps} />
    </Web3ReactProvider>
  );
}

连接 MetaMask

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { useWeb3React } from "@web3-react/core";
import { InjectedConnector } from "@web3-react/injected-connector";

const injected = new InjectedConnector({
  supportedChainIds: [1, 3, 4, 5, 42, 137], // 支持的链ID
});

function WalletConnection() {
  const { active, account, library, connector, activate, deactivate } =
    useWeb3React();

  const connect = async () => {
    try {
      await activate(injected);
    } catch (ex) {
      console.log(ex);
    }
  };

  const disconnect = () => {
    try {
      deactivate();
    } catch (ex) {
      console.log(ex);
    }
  };

  return (
    <div>
      {active ? (
        <div>
          <p>连接地址: {account}</p>
          <button onClick={disconnect}>断开连接</button>
        </div>
      ) : (
        <button onClick={connect}>连接MetaMask</button>
      )}
    </div>
  );
}

高级特性与最佳实践

1. 多钱包支持

在实际项目中,我们通常需要支持多种钱包。这里是一个完整的多钱包连接器配置:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import { InjectedConnector } from "@web3-react/injected-connector";
import { WalletConnectConnector } from "@web3-react/walletconnect-connector";
import { WalletLinkConnector } from "@web3-react/walletlink-connector";

const POLLING_INTERVAL = 12000;
const RPC_URLS = {
  1: process.env.REACT_APP_MAINNET_RPC_URL,
  4: process.env.REACT_APP_RINKEBY_RPC_URL,
};

export const injected = new InjectedConnector({
  supportedChainIds: [1, 3, 4, 5, 42, 137],
});

export const walletconnect = new WalletConnectConnector({
  rpc: { 1: RPC_URLS[1] },
  qrcode: true,
  pollingInterval: POLLING_INTERVAL,
});

export const walletlink = new WalletLinkConnector({
  url: RPC_URLS[1],
  appName: "Your DApp Name",
  appLogoUrl: "https://your-logo-url.com",
});

2. 错误处理

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
import { useWeb3React } from "@web3-react/core";
import { useEffect, useState } from "react";

function useEagerConnect() {
  const { activate, active } = useWeb3React();
  const [tried, setTried] = useState(false);

  useEffect(() => {
    injected.isAuthorized().then((isAuthorized) => {
      if (isAuthorized) {
        activate(injected, undefined, true).catch(() => {
          setTried(true);
        });
      } else {
        setTried(true);
      }
    });
  }, [activate]);

  useEffect(() => {
    if (!tried && active) {
      setTried(true);
    }
  }, [tried, active]);

  return tried;
}

function useInactiveListener(suppress = false) {
  const { active, error, activate } = useWeb3React();

  useEffect(() => {
    const { ethereum } = window;

    if (ethereum && ethereum.on && !active && !error && !suppress) {
      const handleConnect = () => {
        activate(injected);
      };
      const handleChainChanged = (chainId) => {
        activate(injected);
      };
      const handleAccountsChanged = (accounts) => {
        if (accounts.length > 0) {
          activate(injected);
        }
      };

      ethereum.on("connect", handleConnect);
      ethereum.on("chainChanged", handleChainChanged);
      ethereum.on("accountsChanged", handleAccountsChanged);

      return () => {
        ethereum.removeListener("connect", handleConnect);
        ethereum.removeListener("chainChanged", handleChainChanged);
        ethereum.removeListener("accountsChanged", handleAccountsChanged);
      };
    }
  }, [active, error, suppress, activate]);
}

3. 智能合约交互

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import { useWeb3React } from "@web3-react/core";
import { Contract } from "@ethersproject/contracts";
import { useEffect, useState } from "react";

const ERC20_ABI = [
  "function balanceOf(address owner) view returns (uint256)",
  "function transfer(address to, uint256 amount) returns (bool)",
];

function TokenBalance({ tokenAddress }) {
  const { library, account } = useWeb3React();
  const [balance, setBalance] = useState();

  useEffect(() => {
    if (library && account && tokenAddress) {
      const contract = new Contract(tokenAddress, ERC20_ABI, library);

      contract
        .balanceOf(account)
        .then((balance) => {
          setBalance(balance.toString());
        })
        .catch((err) => {
          console.error(err);
        });
    }
  }, [library, account, tokenAddress]);

  return <div>{balance ? `余额: ${balance}` : "加载中..."}</div>;
}

实际项目应用案例

DeFi 协议集成

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
import { useWeb3React } from "@web3-react/core";
import { parseEther } from "@ethersproject/units";

function DeFiInterface() {
  const { library, account } = useWeb3React();

  const handleStake = async (amount) => {
    if (!library || !account) return;

    const signer = library.getSigner();
    const contract = new Contract(
      STAKING_CONTRACT_ADDRESS,
      STAKING_ABI,
      signer
    );

    try {
      const tx = await contract.stake({
        value: parseEther(amount),
      });
      await tx.wait();
      console.log("质押成功!");
    } catch (error) {
      console.error("质押失败:", error);
    }
  };

  return (
    <div>
      <button onClick={() => handleStake("1.0")}>质押 1 ETH</button>
    </div>
  );
}

通常用于:

  • 流动性挖矿平台:用户质押代币获得奖励
  • 质押协议:以太坊 2.0 质押
  • DeFi 收益农场:质押代币或单币获得收益

常见问题与解决方案

1. 网络切换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const switchNetwork = async (chainId) => {
  try {
    await library.provider.request({
      method: "wallet_switchEthereumChain",
      params: [{ chainId: `0x${chainId.toString(16)}` }],
    });
  } catch (switchError) {
    if (switchError.code === 4902) {
      // 网络不存在,需要添加
      await library.provider.request({
        method: "wallet_addEthereumChain",
        params: [networkParams[chainId]],
      });
    }
  }
};

2. 连接状态管理

使用 React Context 来全局管理连接状态,避免 prop drilling:

1
2
3
4
5
6
7
8
9
const Web3Context = createContext();

export const useWeb3Context = () => {
  const context = useContext(Web3Context);
  if (!context) {
    throw new Error("useWeb3Context must be used within Web3Provider");
  }
  return context;
};
This post is licensed under CC BY 4.0 by the author.