教程区块链区块链技术ch1414.4 事件订阅与前端状态同步

本页目录

合约不能主动"推送"数据到前端。但事件日志(Event Log)让前端可以通过轮询、WebSocket 或子图保持与合约状态的同步——正确的同步策略直接影响用户体验。


14.4.1 事件监听的三重境界

策略延迟复杂度适用场景
手动轮询5-15 秒简单页面,低并发
WebSocket 订阅实时实时 DApp,高频更新
The Graph 子图(Subgraph)5-30 秒较高需要复杂查询/聚合
graph TB
    subgraph Polling["手动轮询"]
        P1[setInterval 5s] --> P2[eth_call getBalance]
        P2 --> P3[更新 React state]
    end
    
    subgraph WebSocket["WebSocket"]
        W1[eth_subscribe] --> W2[eth_newFilter]
        W2 --> W3[推送事件到前端]
        W3 --> W4[通过 Redux/Zustand 分发]
    end
    
    subgraph TheGraph["The Graph"]
        T1[GraphQL 查询] --> T2[索引节点聚合]
        T2 --> T3[返回结构化数据]
    end
    
    Polling --"简单" --> WebSocket
    WebSocket --"复杂查询" --> TheGraph
    
    style Polling fill:#ffebee
    style WebSocket fill:#e8f5e9
    style TheGraph fill:#fff3e0

14.4.2 事件监听核心模式

typescript
/**
 * 事件驱动的状态同步模式
 */
interface EthersContract {
  on(event: string, handler: (...args: any[]) => void): void;
  removeAllListeners(event?: string): void;
  queryFilter(event: string, fromBlock: number, toBlock: number): Promise<any[]>;
}

class EventSyncManager {
  private contract: EthersContract;
  private lastBlock: number = 0;
  private handlers: Map<string, Function[]> = new Map();
  private active: boolean = true;
  
  constructor(contract: EthersContract) {
    this.contract = contract;
  }
  
  // 实时事件监听器
  subscribe(eventName: string, handler: (event: any) => void) {
    this.contract.on(eventName, (...args) => {
      if (!this.active) return;
      const parsed = this.parseEvent(eventName, args);
      handler(parsed);
    });
    
    // 必须清理:避免内存泄漏
    return () => this.contract.removeAllListeners(eventName);
  }
  
  // 历史回溯(当用户首次打开页面时)
  async backfill(eventName: string, fromBlock: number): Promise<any[]> {
    const events = await this.contract.queryFilter(eventName, fromBlock, 'latest');
    return events.map(e => this.parseEvent(eventName, e.args));
  }
  
  // 组合:先回溯历史 + 再监听新事件
  async sync(eventName: string, fromBlock: number, handler: (e: any) => void) {
    const historical = await this.backfill(eventName, fromBlock);
    for (const e of historical) handler(e);
    
    const cleanup = this.subscribe(eventName, handler);
    return cleanup;
  }
  
  private parseEvent(name: string, rawArgs: any[]): any {
    return { name, args: rawArgs, timestamp: Date.now() };
  }
  
  // 组件卸载时清理!
  destroy() {
    this.active = false;
    this.contract.removeAllListeners();
  }
}

// React 键实践
function useContractSync(contract: EthersContract, eventName: string) {
  const [events, setEvents] = React.useState<any[]>([]);
  const manager = new EventSyncManager(contract);
  
  React.useEffect(() => {
    // 清理函数:组件卸载时取消所有监听
    return manager.sync(eventName, 0, (e: any) => {
      setEvents(prev => [...prev, e]);
    });
  }, [contract, eventName]);
  
  return events;
}

14.4.3 The Graph 子图查询

typescript
/**
 * GraphQL 查询:获取聚合数据而不手动同步
 */
interface SubgraphQuery {
  // 查询 Aave 的所有借贷池活动和 TVL
  query: string;
  endpoint: string; // 如 https://api.thegraph.com/subgraphs/name/aave/protocol-v3
}

// 比手动解析事件更强大的查询能力
const AAVE_OVERVIEW_QUERY = `
  {
    reserves(first: 10, orderBy: totalATokenSupply, orderDirection: desc) {
      id
      name
      symbol
      decimals
      totalATokenSupply
      totalBorrowed
      liquidityRate
      variableBorrowRate
    }
  }
`;

// 对比:
// 手动监听 Pool.Borrow 事件 → 需要在前端做聚合和
// GraphQL 查询 → 索引器已做好聚合,前端直接消费
// 劣势:The Graph 的索引有 5-30s 延迟,不是实时的

三种策略的权衡矩阵

需求推荐
需要最新数据(< 5s)直接 eth_call 或 WebSocket
需要历史聚合(过去 30 天 TVL)The Graph 子图
需要复杂筛选子图 GraphQL
需要不依赖中心化索引自建 RPC 轮询
最小化代码复杂度eth_subscribe

> ← 14.3 交易构造 | 前往 → 14.5 去中心化存储与前端 |*

评论

0

评论加载中…

发表评论

0/2000