Web3:在 VSCode 中使用 Vue 前端与已部署的 Solidity 智能合约进行交互

发布于:2025-08-01 ⋅ 阅读:(17) ⋅ 点赞:(0)

相关文章推荐 链接
Web3专栏 https://blog.csdn.net/qq_42392981/category_13016259.html

引言

如果你已经掌握了 Solidity 智能合约的编写、测试和本地部署 Web3:在 VSCode 中基于 Foundry 快速构建 Solidity 智能合约本地开发环境,下一步便是构建前端界面来与之交互。本教程聚焦在 VSCode 环境中使用 React.js 框架调用已部署的合约,适合 Web3 初学者。我们将以一个简单的 Counter 合约为例,演示如何使用 ethers.js 库在 React 组件中读取合约状态、调用函数,并处理交易响应。整个过程强调交互细节、错误处理和调试技巧,确保你能构建一个可靠的前端 DApp 界面。

前提条件


1.1 启动本地测试网络(如果使用 Anvil)

前面的文章已经介绍了怎么部署Web3:在 VSCode 中基于 Foundry 快速构建 Solidity 智能合约本地开发环境

假设合约已部署到本地 Anvil:

  1. 在 VSCode 终端 Git bash 中运行:
    anvil --accounts 10 --balance 100
    
  2. 记录 RPC URL(http://127.0.0.1:8545)和测试账户私钥(用于签名交易)。

1.2 获取合约 ABI

ABI 是合约接口定义,用于前端调用。从 Foundry 项目中提取:

  1. 在 Foundry 项目目录运行:
    forge build
    
  2. ABI 文件位于 out/Counter.sol/Counter.json 中的 “abi” 字段。复制 ABI 数组:
[
    {
      "type": "function",
      "name": "increment",
      "inputs": [],
      "outputs": [],
      "stateMutability": "nonpayable"
    },
    {
      "type": "function",
      "name": "number",
      "inputs": [],
      "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }],
      "stateMutability": "view"
    },
    {
      "type": "function",
      "name": "setNumber",
      "inputs": [
        { "name": "newNumber", "type": "uint256", "internalType": "uint256" }
      ],
      "outputs": [],
      "stateMutability": "nonpayable"
    }
  ]

**建议截图位置**:ABI JSON 文件在 VSCode 中的视图。


2. 创建 React 项目

2.1 初始化项目

  1. 在 VSCode 终端导航到项目目录(例如 C:\Projects)。
  2. 创建 React 项目:
    npx create-react-app react-contract-interaction
    cd react-contract-interaction
    
  3. React 目录结构
    在这里插入图片描述

2.2 安装 ethers.js

安装 ethers.js 以处理合约交互:

npm install ethers

2.3 项目结构调整

  • 创建 src/contracts/ 目录,添加 CounterABI.js
    export const COUNTER_ABI = [
      {
        "type": "function",
        "name": "increment",
        "inputs": [],
        "outputs": [],
        "stateMutability": "nonpayable"
      },
      {
        "type": "function",
        "name": "number",
        "inputs": [],
        "outputs": [{ "name": "", "type": "uint256", "internalType": "uint256" }],
        "stateMutability": "view"
      },
      {
        "type": "function",
        "name": "setNumber",
        "inputs": [
          { "name": "newNumber", "type": "uint256", "internalType": "uint256" }
        ],
        "outputs": [],
        "stateMutability": "nonpayable"
      }
    ];
    export const COUNTER_ADDRESS = '0x5FbDB2315678afecb367f032d93F642f64180aa3'; // 替换为实际合约地址
    export const RPC_URL = 'http://127.0.0.1:8545'; // 测试网 RPC
    

3. 编写 React 组件与合约交互

3.1 创建交互组件

src/components/ 下创建 CounterInteraction.js(使用类组件或函数组件;这里用 hooks 的函数组件)。这个组件将:

  • 使用 useState 管理状态。
  • 使用 useEffect 初始化连接。
  • 连接到提供者(Provider)。
  • 使用签名者(Signer)调用写函数。
  • 读取合约状态。
  • 处理加载状态和错误。

完整代码:

import React, { useState, useEffect } from 'react';
import { ethers } from 'ethers';
import { COUNTER_ABI, COUNTER_ADDRESS, RPC_URL } from '../contracts/CounterABI';

const CounterInteraction = () => {
  const [number, setNumber] = useState(0);
  const [newNumber, setNewNumber] = useState('');
  const [loading, setLoading] = useState(false);
  const [error, setError] = useState('');
  const [txHash, setTxHash] = useState('');
  const [contract, setContract] = useState(null);

  useEffect(() => {
    const initialize = async () => {
      try {
        // 初始化提供者(无签名,用于读取)
        const provider = new ethers.JsonRpcProvider(RPC_URL);
        
        // 初始化签名者(使用私钥,用于写入;生产环境用钱包如 MetaMask)
        const privateKey = '0xYourPrivateKeyFromAnvil'; // 替换为 Anvil 私钥,安全起见勿硬编码
        const signer = new ethers.Wallet(privateKey, provider);
        
        // 初始化合约实例(连接签名者以支持写入)
        const contractInstance = new ethers.Contract(COUNTER_ADDRESS, COUNTER_ABI, signer);
        setContract(contractInstance);
        
        await fetchNumber(contractInstance);
      } catch (err) {
        setError('初始化失败: ' + err.message);
      }
    };
    initialize();
  }, []);

  const fetchNumber = async (contractInstance) => {
    setLoading(true);
    try {
      const currentNumber = (await contractInstance.number()).toString();
      setNumber(currentNumber);
      setError('');
    } catch (err) {
      setError('读取失败: ' + err.message);
    } finally {
      setLoading(false);
    }
  };

  const increment = async () => {
    if (!contract) return;
    setLoading(true);
    try {
      const tx = await contract.increment();
      await tx.wait(); // 等待交易确认
      setTxHash(tx.hash);
      await fetchNumber(contract);
    } catch (err) {
      setError('递增失败: ' + err.message);
    } finally {
      setLoading(false);
    }
  };

  const handleSetNumber = async () => {
    if (!contract || !newNumber) return;
    setLoading(true);
    try {
      const tx = await contract.setNumber(newNumber);
      await tx.wait();
      setTxHash(tx.hash);
      await fetchNumber(contract);
      setNewNumber('');
    } catch (err) {
      setError('设置失败: ' + err.message);
    } finally {
      setLoading(false);
    }
  };

  return (
    <div className="counter-container">
      <h2>与 Counter 合约交互</h2>
      {loading ? <p>加载中...</p> : <p>当前数值: {number}</p>}
      <button onClick={increment} disabled={loading}>递增</button>
      <input
        type="number"
        value={newNumber}
        onChange={(e) => setNewNumber(e.target.value)}
        placeholder="设置新值"
      />
      <button onClick={handleSetNumber} disabled={loading}>设置数值</button>
      {error && <p className="error">{error}</p>}
      {txHash && <p>最近交易哈希: {txHash}</p>}
    </div>
  );
};

export default CounterInteraction;

解释细节

  • Hooks 使用useState 管理本地状态,useEffect 处理初始化(仅运行一次)。
  • Provider vs Signer:Provider 用于只读调用(如 number()),Signer 用于写入(如 increment()),需私钥签名。
  • 异步处理:使用 async/await 管理交易,tx.wait() 等待区块确认。
  • 错误处理:捕获异常(如 gas 不足、网络错误),显示用户友好消息。
  • 加载状态:禁用按钮防止重复点击。
  • 安全性提示:本地开发用私钥硬编码;生产环境集成 MetaMask,使用 window.ethereum 请求签名。

3.2 集成到主应用

编辑 src/App.js

import React from 'react';
import CounterInteraction from './components/CounterInteraction';
import './App.css';

function App() {
  return (
    <div className="App">
      <CounterInteraction />
    </div>
  );
}

export default App;

添加简单 CSS 到 src/App.css

.App { text-align: center; margin-top: 50px; }
.counter-container { margin: 20px; }
.error { color: red; }

3.3 启动应用

在 VSCode 终端运行:

npm start

访问 http://localhost:3000,测试交互:

  • 点击“递增”:数值 +1。
  • 输入并设置:更新数值。

界面如下图所示:
在这里插入图片描述

结语

在 VSCode 中使用 React 调用 Solidity 合约的核心流程:从初始化连接到处理读写操作和错误。实践这些步骤,能轻松扩展到更复杂的 DApp,如 NFT 市场或 DeFi 界面。记住,生产部署时优先使用钱包集成,并审计合约安全。


网站公告

今日签到

点亮在社区的每一天
去签到