在 VSCode 中使用 React 前端与已部署的 Solidity 智能合约进行交互
-
- 引言
-
- [1.1 启动本地测试网络(如果使用 Anvil)](#1.1 启动本地测试网络(如果使用 Anvil))
- [1.2 获取合约 ABI](#1.2 获取合约 ABI)
- [2. 创建 React 项目](#2. 创建 React 项目)
-
- [2.1 初始化项目](#2.1 初始化项目)
- [2.2 安装 ethers.js](#2.2 安装 ethers.js)
- [2.3 项目结构调整](#2.3 项目结构调整)
- [3. 编写 React 组件与合约交互](#3. 编写 React 组件与合约交互)
-
- [3.1 创建交互组件](#3.1 创建交互组件)
- [3.2 集成到主应用](#3.2 集成到主应用)
- [3.3 启动应用](#3.3 启动应用)
- 结语
相关文章推荐 | 链接 |
---|---|
Web3专栏 | https://blog.csdn.net/qq_42392981/category_13016259.html |
引言
如果你已经掌握了 Solidity 智能合约的编写、测试和本地部署 Web3:在 VSCode 中基于 Foundry 快速构建 Solidity 智能合约本地开发环境,下一步便是构建前端界面来与之交互。本教程聚焦在 VSCode 环境中使用 React.js 框架调用已部署的合约,适合 Web3 初学者。我们将以一个简单的 Counter 合约为例,演示如何使用 ethers.js 库在 React 组件中读取合约状态、调用函数,并处理交易响应。整个过程强调交互细节、错误处理和调试技巧,确保你能构建一个可靠的前端 DApp 界面。
前提条件:
- 已部署 Counter 合约(本地 Anvil 网络或其他测试网),并记录合约地址和 ABI。
- Windows 10 或更高版本。
- 已安装 VSCode、Node.js 和 Foundry(参考前文: Web3:在 VSCode 中基于 Foundry 快速构建 Solidity 智能合约本地开发环境)。
- 基本 React 和 JavaScript 知识。
1.1 启动本地测试网络(如果使用 Anvil)
前面的文章已经介绍了怎么部署
: Web3:在 VSCode 中基于 Foundry 快速构建 Solidity 智能合约本地开发环境
假设合约已部署到本地 Anvil:
-
在 VSCode 终端 Git bash 中运行:
gitanvil --accounts 10 --balance 100
-
记录 RPC URL(
http://127.0.0.1:8545
)和测试账户私钥(用于签名交易)。
1.2 获取合约 ABI
ABI 是合约接口定义,用于前端调用。从 Foundry 项目中提取:
-
在 Foundry 项目目录运行:
powershellforge build
-
ABI 文件位于
out/Counter.sol/Counter.json
中的 "abi" 字段。复制 ABI 数组:
json
[
{
"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"
}
]

2. 创建 React 项目
2.1 初始化项目
-
在 VSCode 终端导航到项目目录(例如
C:\Projects
)。 -
创建 React 项目:
powershellnpx create-react-app react-contract-interaction cd react-contract-interaction
-
React 目录结构
2.2 安装 ethers.js
安装 ethers.js 以处理合约交互:
powershell
npm install ethers
2.3 项目结构调整
-
创建
src/contracts/
目录,添加CounterABI.js
:javascriptexport 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)调用写函数。
- 读取合约状态。
- 处理加载状态和错误。
完整代码:
jsx
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
:
jsx
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
:
css
.App { text-align: center; margin-top: 50px; }
.counter-container { margin: 20px; }
.error { color: red; }
3.3 启动应用
在 VSCode 终端运行:
powershell
npm start
访问 http://localhost:3000
,测试交互:
- 点击"递增":数值 +1。
- 输入并设置:更新数值。
界面如下图所示:
结语
在 VSCode 中使用 React 调用 Solidity 合约的核心流程:从初始化连接到处理读写操作和错误。实践这些步骤,能轻松扩展到更复杂的 DApp,如 NFT 市场或 DeFi 界面。记住,生产部署时优先使用钱包集成,并审计合约安全。