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) 才轮询,后台挂起时暂停请求。
相关推荐
RoyLin2 小时前
TypeScript设计模式:代理模式
前端·后端·typescript
IT_陈寒3 小时前
Vue3性能优化实战:这5个技巧让我的应用加载速度提升了70%
前端·人工智能·后端
树上有只程序猿3 小时前
react 实现插槽slot功能
前端
stoneship4 小时前
Web项目减少资源加载失败白屏问题
前端
DaMu4 小时前
Cesium & Three.js 【移动端手游“户外大逃杀”】 还在“画页面的”前端开发小伙伴们,是时候该“在往前走一走”了!我们必须摆脱“画页面的”标签!
前端·gis
非专业程序员4 小时前
一文读懂Font文件
前端
Asort4 小时前
JavaScript 从零开始(七):函数编程入门——从定义到可重用代码的完整指南
前端·javascript
Johnny_FEer4 小时前
什么是 React 中的远程组件?
前端·react.js