Web3:在 VSCode 中使用 Vue 前端与已部署的 Solidity 智能合约进行交互
在 VSCode 中使用 React 前端与已部署的 Solidity 智能合约进行交互
- 引言
- 1.1 启动本地测试网络(如果使用 Anvil)
- 1.2 获取合约 ABI
- 2. 创建 React 项目
- 2.1 初始化项目
- 2.2 安装 ethers.js
- 2.3 项目结构调整
- 3. 编写 React 组件与合约交互
- 3.1 创建交互组件
- 3.2 集成到主应用
- 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 中运行:
anvil --accounts 10 --balance 100
- 记录 RPC URL(
http://127.0.0.1:8545
)和测试账户私钥(用于签名交易)。
1.2 获取合约 ABI
ABI 是合约接口定义,用于前端调用。从 Foundry 项目中提取:
- 在 Foundry 项目目录运行:
forge build
- 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"}]
2. 创建 React 项目
2.1 初始化项目
- 在 VSCode 终端导航到项目目录(例如
C:\Projects
)。 - 创建 React 项目:
npx create-react-app react-contract-interaction cd react-contract-interaction
- 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><inputtype="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 界面。记住,生产部署时优先使用钱包集成,并审计合约安全。