如何通过web3.js在React前端调用智能合约函数访问结构体数据
React + web3.js 调用智能合约及访问结构体数据实现方案
核心问题原因
你当前仅能成功调用admin的核心原因是调用语法不匹配web3.js版本:contract.xxx({from: addr})是web3.js 0.x版本的旧语法,1.0及以上版本要求所有合约方法、状态变量的调用必须走contract.methods.xxx(入参).call(配置)的格式。
除此之外还有几个会导致调用失败的潜在问题:
- 贴出的Solidity代码存在语法缺失:
onlyOwner修饰符未定义,会导致合约编译部署失败 - 变量名使用了易冲突的关键字:
add是Solidity内置方法名、data是web3返回值通用属性名,容易引发异常 - 未正确处理合约返回的uint类型:Solidity返回的整数默认是BigNumber格式,直接使用会出现类型错误
- ABI或合约地址不匹配:修改合约后未重新编译部署、前端引用的ABI和链上合约版本不一致
第一步:修复Solidity合约(可正常编译版本)
先补全合约缺失的逻辑,调整易冲突的变量名:
// SPDX-License-Identifier: MIT pragma solidity >=0.4.22 <0.9.0; struct Customer { uint256 amount; } contract Lottery { address payable public admin; // 原变量名add和内置方法重名,改名避免冲突 address payable[] public addList; // 原变量名data过于通用,改名 mapping(address => Customer) public userData; // 补全缺失的onlyOwner修饰符 modifier onlyOwner() { require(msg.sender == admin, "not admin"); _; } constructor() { admin = payable(msg.sender); } function getBalance() public view onlyOwner returns (uint256) { return address(this).balance / 1 ether; } // 原方法名getter语义不清晰,调整为见名知意的命名 function getAddListLength() view public returns (uint) { return addList.length; } // 测试用:参与抽奖方法 function join() external payable { require(msg.value > 0, "need to send ether"); addList.push(payable(msg.sender)); userData[msg.sender].amount = msg.value; } }
合约修改后重新编译、部署到链上,记录新的合约地址和编译生成的ABI文件,供前端引用。
第二步:React侧正确实现
初始化web3与合约实例
import { useState, useEffect } from 'react'; import Web3 from 'web3'; // 引入重新编译后生成的合约ABI文件 import LotteryABI from './Lottery.json'; // 替换为你自己部署后的合约地址 const CONTRACT_ADDR = "0x替换成你的合约部署地址"; function LotteryApp() { const [web3Api, setWeb3Api] = useState({ web3: null, contract: null, account: null }); const [admin, setAdmin] = useState(""); const [listLength, setListLength] = useState(0); const [userStakeAmount, setUserStakeAmount] = useState(0); const [contractBalance, setContractBalance] = useState(0); // 初始化web3连接和合约实例 useEffect(() => { const init = async () => { let web3; // 连接小狐狸等注入钱包 if (window.ethereum) { web3 = new Web3(window.ethereum); await window.ethereum.request({ method: 'eth_requestAccounts' }); } else { // 本地测试节点(如Ganache)走RPC连接 web3 = new Web3("http://127.0.0.1:7545"); } const accounts = await web3.eth.getAccounts(); // 初始化合约实例 const contract = new web3.eth.Contract(LotteryABI.abi, CONTRACT_ADDR); setWeb3Api({ web3, contract, account: accounts[0] }); } init(); }, []);
合约方法与结构体数据调用
// 拉取合约数据 useEffect(() => { const fetchData = async () => { const { web3, contract, account } = web3Api; if (!web3 || !contract || !account) return; // 1. 读取public状态变量admin const adminRes = await contract.methods.admin().call({ from: account }); setAdmin(adminRes); // 2. 调用view方法获取地址数组长度 const lengthRes = await contract.methods.getAddListLength().call(); // uint返回值为BigNumber类型,转成普通数字使用 setListLength(Number(lengthRes)); // 3. 访问mapping中存储的结构体数据 // public类型的mapping会自动生成getter,传入mapping的key(这里是用户地址)即可返回对应结构体 const userDataRes = await contract.methods.userData(account).call(); // 结构体返回值是和字段名一一对应的对象,直接取属性即可,这里转成ETH单位方便展示 setUserStakeAmount(web3.utils.fromWei(userDataRes.amount, 'ether')); // 4. 调用带权限校验的方法,from必须传入admin地址,否则会被onlyOwner拦截 if (account.toLowerCase() === adminRes.toLowerCase()) { const balanceRes = await contract.methods.getBalance().call({ from: account }); setContractBalance(Number(balanceRes)); } } fetchData(); }, [web3Api.contract, web3Api.account]); return ( <div> <p>合约管理员地址:{admin}</p> <p>参与人数:{listLength}</p> <p>当前用户质押金额:{userStakeAmount} ETH</p> <p>合约余额:{contractBalance} ETH</p> </div> ) } export default LotteryApp;
调用注意事项
- 所有
view/pure方法、public状态变量的读取,统一使用.call()发送调用,不需要消耗gas;修改链上状态的方法使用.send(),需要指定from账户并消耗gas - 访问Solidity自动生成的getter规则:
- 数组需要传入索引值,例如
addList(0)即可获取下标为0的地址 - mapping需要传入对应的key值,即可返回对应value,如果value是结构体,直接返回字段名匹配的对象
- 结构体如果是嵌套类型,按层级取属性即可
- 数组需要传入索引值,例如
- 合约修改后必须重新编译、重新部署,前端同步更新ABI和合约地址,否则会出现方法不存在的报错
- 不要使用Solidity保留字、内置方法名作为变量/函数名,避免出现难以排查的异常
内容的提问来源于stack exchange,提问作者Tarandeep singh
相关产品推荐
相关产品推荐

