如何在React useEffect中正确使用Solidity事件?解决重复订阅问题
解决React useEffect中Solidity事件监听器订阅与取消的问题
核心问题分析
你的代码中每次调用counterContract.events.Increment()或Decrement()都会生成全新的事件实例,订阅时用的是一个实例,清理时又创建了另一个新实例调用unsubscribe/removeAllListeners,自然无法取消之前的订阅。再加上React开发环境下的严格模式会刻意执行两次useEffect(先执行订阅+清理,再重新订阅),导致残留多个无效监听器。
正确解决方案
方案1:保存事件实例,复用实例执行取消操作
将订阅时的事件实例保存到变量中,清理时直接对同一个实例执行取消操作:
useEffect(() => { console.log('ADD'); // 保存事件实例,避免每次调用生成新对象 const incrementEvent = counterContract.events.Increment(); const decrementEvent = counterContract.events.Decrement(); // 绑定监听器 incrementEvent.on('data', () => ReadContractValue()); decrementEvent.on('data', () => ReadContractValue()); return () => { console.log('REMOVE'); // 对同一个实例执行取消订阅 incrementEvent.unsubscribe(); decrementEvent.unsubscribe(); }; }, []);
方案2:直接使用订阅返回的取消函数(推荐,以Ethers.js为例)
如果你的合约库是Ethers.js,on方法会直接返回一个取消订阅的函数,直接保存并调用即可,无需管理事件实例:
useEffect(() => { console.log('ADD'); // 订阅事件并保存取消函数 const unsubscribeIncrement = counterContract.events.Increment().on('data', () => ReadContractValue()); const unsubscribeDecrement = counterContract.events.Decrement().on('data', () => ReadContractValue()); return () => { console.log('REMOVE'); // 调用取消函数完成清理 unsubscribeIncrement(); unsubscribeDecrement(); }; }, []);
额外说明
- React严格模式下的两次
ADD/REMOVE是开发环境专属的检测行为,用于排查未正确清理的副作用,生产环境只会执行一次订阅。只要清理函数正确,第二次订阅的监听器会覆盖无效的旧监听器,不会导致逻辑问题。 - 若需要移除特定监听器而非全部,可使用
removeListener方法,但需保存绑定的回调函数引用(避免匿名函数导致无法匹配):useEffect(() => { const handleEvent = () => ReadContractValue(); const incrementEvent = counterContract.events.Increment(); incrementEvent.on('data', handleEvent); return () => { incrementEvent.removeListener('data', handleEvent); }; }, []);
内容的提问来源于stack exchange,提问作者Alex Csiszár
相关产品推荐
相关产品推荐

