Web3基础

1、前端如何做多链支持(Ethereum、Polygon、BSC 等)?

答:前端要支持多链,就要解决:

  • 如何连接不同链(Ethereum / Polygon / BSC ...)
  • 如何获取链上数据(余额、交易、合约交互)
  • 如何在链之间切换(用户体验 + 钱包交互)

常见做法

  • 多链RPC Provider
    • 前端可以为每条链准备不同的RPC URL
    • 根据用户选择的链,切换使用的provider
js 复制代码
import { JsonRpcProvider } from 'ethers';
const providers = {
  ethereum: new JsonRpcProvider('https://mainnet.infura.io/v3/xxx'),
  polygon: new JsonRpcProvider('https://polygon-rpc.com'),
  bsc: new JsonRpcProvider('https://bsc-dataseed.binance.org/')
}  
  • 检测 & 切换链(MetaMask 等钱包)
    • 使用window.ethereum.request({ method: "eth_chainId" })获取当前钱包所连链。
    • 如果用户在Polygon页签,但钱包在Ethereum → 提示切换。
    • 使用wallet_switchEthereumChainwallet_addEthereumChain让用户在钱包里一键切换:
js 复制代码
await window.ethereum.request({
  method: 'wallet_switchEthereumChain',
  params: [{ chainId: '0x89' }] // Polygon chainId
})
  • 多链合约配置
    • 同一个DApp的合约地址可能在不同链上不同。
    • 前端需要维护一个chainId → contractAddress的映射表:
js 复制代码
const contractAddresses = {
  1: '0xEthereumContract',
  137: '0xPolygonContract',
  56: '0xBscContract'
}
  • 多链Token显示:对于钱包类应用,需要动态获取账户在不同链上的 Token:
    • 方式一:调用链上标准接口(如ERC-20balanceOf)。
    • 方式二:集成 第三方 API(如 Alchemy、Covalent、Moralis),一次返回某地址在多条链的资产。

用户体验优化

  • 自动识别链:进入DApp时,自动检测当前链是否匹配,如果不对 → 引导切换。
  • 统一状态管理:通过zustand / redux存储chainIdprovider,保证全局一致。
  • 多链兼容组件:写通用useContract(chainId, abi) hook,动态返回不同链的合约实例。

坑点

  • 不同链的 gas机制、手续费单位 不一样(Polygon/BSC 用 gwei,但实际消耗很低)。
  • 有些链 RPC 不稳定,需要备用节点。
  • 浏览器钱包可能不支持某些侧链 (要用wallet_addEthereumChain)。

2、如何处理链上数据缓存,避免重复请求?

答:链上数据(余额、价格、交易状态)往往变化频率不高,但请求成本高、延迟大,如果不做缓存,很容易造成重复请求、页面卡顿、节点限流

为什么要缓存链上数据?

  • RPC 调用慢:一次eth_callgetBalance通常要几十~几百毫秒。
  • 节点限流:像Infura、Alchemy免费套餐有QPS限制,过多请求会被block
  • 数据变化频率低:地址余额不会每秒变,重复请求没有意义。

常见缓存手段

  • 内存缓存(短时缓存):前端直接在内存里缓存请求结果,避免同一页面内重复调用。
  • 持久化缓存(localStorage / IndexedDB):适合 代币列表、合约 ABI 等不常更新的数据。
  • 请求合并(Deduplication):用SWR / React Query这类库,能自动避免重复请求:
    • 相同 key 的请求只会发一次;
    • 自动缓存结果,支持stale-while-revalidate
    • 内置请求重试、失效策略。
jsx 复制代码
import useSWR from 'swr';
import { ethers } from 'ethers';
const fetchBalance = async (address: string) => {
  const provider = new ethers.providers.JsonRpcProvider(process.env.RPC_URL);
  return await provider.getBalance(address);
};
function Balance({ address }) {
  const { data: balance } = useSWR(
    ['balance', address],
    () => fetchBalance(address),
    { refreshInterval: 30_000 } // 30s 自动刷新
  );
  return <div>{balance && ethers.utils.formatEther(balance)}</div>;
}
  • 区块级缓存(block-level cache):链上数据只会在新区块出现时才会变化。因此缓存策略可以按区块号来做:
    • 获取当前blockNumber
    • 只在新区块出现时才重新请求。
jsx 复制代码
provider.on('block', async blockNumber => {
  const balance = await provider.getBalance(address);
  console.log(`区块 ${blockNumber} 余额更新:`, balance.toString());
})
  • 使用第三方 API 的聚合缓存:像Alchemy、Covalent、Moralis提供的余额、代币价格API,本身就做了缓存和索引,速度远快于自己扫链。
    • 适合需要 批量地址、批量 token 查询;
    • 避免前端对RPC节点的高频调用。

3、如何在前端处理代币价格或余额的异步刷新?

异步刷新面临的问题

  • 数据延迟:区块链节点返回的余额是实时的,但频繁请求会导致卡顿/性能差。
  • 价格获取:代币价格不是链上数据,需要从**预言机(如 Chainlink)或第三方 API(CoinGecko、Covalent、Alchemy)**获取。
  • 频繁更新:移动端/弱网环境下,过多请求很影响体验。

常见处理方案

  • 余额刷新

    • 定时轮询:比如每 15~30 秒调用一次provider.getBalance(address)或合约balanceOf
    jsx 复制代码
    import { ethers } from 'ethers';
    async function fetchBalance(provider, address, tokenAddress) {
      if (!tokenAddress) {
        // 原生代币(ETH)
        return await provider.getBalance(address);
      }else {
        // ERC20
        const abi = ['function balanceOf(address) view returns (uint256)'];
        const token = new ethers.Contract(tokenAddress, abi, provider);
        return await token.balanceOf(address);
      }
    }
    • 区块事件监听:provider.on("block", callback)每出一个新区块时去更新余额,更加精准:
    jsx 复制代码
    provider.on('block', async blockNumber => {
      const balance = await provider.getBalance(address);
      console.log('最新余额', ethers.utils.formatEther(balance));
    })
    • 链下服务:使用Alchemy、Moralis等提供的 余额聚合API,能一次拿到所有代币余额,而不是自己逐个调用balanceOf
    jsx 复制代码
    import Moralis from 'moralis';
    const getBalance = async () => {
      try {
        await Moralis.start({ apiKey: apiKey})
        const response = await Moralis.EvmApi.wallets.getWalletTokenBalancesPrice({
        chain: toBeHex(8453), // base 主链
        address: '0x...B7' // 要查询的地址
        })
        console.log('raw', response.result);
      }catch (e) {
       console.error(e);
     }
    }
  • 价格刷新

    • 第三方 API:使用CoinGecko、Binance API、Covalent API获取实时价格,定时轮询(10~60s 一次即可)。
    • 链上预言机:对于关键交易(如 swap),用Chainlink Aggregator合约读价格,避免依赖第三方 API。
    jsx 复制代码
    // 通过 链上预言机合约读取价格  ETH/USD
    import { Contract, JsonRpcProvider, formatUnits } from 'ethers';
    import AggregatorV3ABI from '@chainlink/contracts/abi/v0.8/AggregatorV3Interface.json';
    const ETH_USD_PRICE_FEED = '0x5f4ec3df9cbd43714fe2740f5e3616155c5b8419';
    const getEthUsdPrice = async () => {
      // 连接 RPC 节点
      const provider = new JsonRpcProvider(ethRpc?.rpcUrl);
      // 创建合约对象
      const priceFeed = new Contract(ETH_USD_PRICE_FEED, AggregatorV3ABI, provider);
      // 调用最新价格
      const roundData = await priceFeed.latestRoundData();
      // 获取小数位
      const decimals = await priceFeed.decimals();
      console.log('decimals', decimals);
      // 格式化价格
      const price: any = formatUnits(roundData.answer, decimals);
      console.log('ETH/USD 价格:', price);
    }

性能优化

  • 请求合并:如果需要多个代币余额,可以用Multicall合约 一次性请求,减少RPC请求数。
  • 节流/防抖:避免用户频繁切换页面时触发过多请求。
  • 状态管理:用React Query / SWR / Zustand来缓存请求结果,避免相同数据重复请求。
  • 条件刷新:前端可以在 页面可见时(Page Visibility API) 才轮询,后台挂起时暂停请求。
相关推荐
Lupino24 分钟前
被 React “玩弄”的 24 小时:为了修一个不存在的 Bug,我给大模型送了顿火锅钱
前端·react.js
米丘30 分钟前
了解 Javascript 模块化,更好地掌握 Vite 、Webpack、Rollup 等打包工具
前端
Heo32 分钟前
深入 React19 Diff 算法
前端·javascript·面试
滕青山33 分钟前
个人所得税计算器 在线工具核心JS实现
前端·javascript·vue.js
小怪点点33 分钟前
手写promise
前端·promise
国思RDIF框架42 分钟前
RDIFramework.NET Web 敏捷开发框架 V6.3 发布 (.NET8+、Framework 双引擎)
前端
Mintopia43 分钟前
如何在有限的时间里,活出几倍的人生
前端
炫饭第一名44 分钟前
速通Canvas指北🦮——变形、渐变与阴影篇
前端·javascript·程序员
Neptune11 小时前
让我带你迅速吃透React组件通信:从入门到精通(上篇)
前端·javascript
阿懂在掘金1 小时前
Vue 表单避坑(一):为什么 v-model 绑定对象属性会偷偷修改父组件数据?
前端·vue.js