You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何通过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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 14:30:57