合约不能主动"推送"数据到前端。但事件日志(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评论加载中…