两个空依赖useEffect首个未执行:原因排查与修复方案
问题与解决方案
问题
我声明了两个依赖项为空数组的useEffect,但排在第一位的useEffect始终无法执行;若调换两者顺序,仍是位于首位的useEffect不执行。代码如下:
import React, {createContext, useState, useEffect, useRef, useContext} from 'react' import { ethers } from 'ethers' import Web3Modal from 'web3modal' import { useNavigate } from 'react-router-dom' import { ABI, ADDRESS } from '../contract' const GlobalContext = createContext() export const GlobalContextProvider = ({children})=>{ const [walletAddress, setWalletAddress] = useState('') const [provider, setprovider] = useState('') const [contract, setcontract] = useState(null) const [showAlert, setshowAlert] = useState({status: false, type: 'info', message: ''}) const updateCurrentWalletAddress = async ()=>{ const accounts = await window.ethereum.request({method : 'eth_requestAccounts'}) console.log("Hello from updateCurrentWalletAddress") if(accounts) setWalletAddress(accounts[0]) } useEffect(() => { updateCurrentWalletAddress() window.ethereum.on('accountsChanged', updateCurrentWalletAddress) }, []) useEffect(() => { const setsmartContractAndProvider = async ()=>{ const web3Modal = new Web3Modal() const connection = await web3Modal.connect() const newProvider = new ethers.providers.Web3Provider(connection) const signer = newProvider.getSigner() console.log("Hello from setsmartContractAndProvider") console.log({signer}) console.log({walletAddress}) const newContract = new ethers.Contract(ADDRESS, ABI, signer) setprovider(newProvider) setcontract(newContract) } setsmartContractAndProvider() }, []) useEffect(() => { if(showAlert?.status){ const timer = setTimeout(()=>{ setshowAlert ({status: false, type: 'info', message: ''}) }, [5000]) return ()=> clearTimeout(timer) } }, [showAlert]) return( <GlobalContext.Provider value={{ contract, walletAddress, showAlert, setshowAlert }}> {children} </GlobalContext.Provider> ) } export const useGlobalContext = ()=> useContext(GlobalContext)
原因分析
核心问题是两个同时触发的钱包授权请求冲突:
- 第一个
useEffect里的updateCurrentWalletAddress调用了window.ethereum.request({method: 'eth_requestAccounts'}),会触发钱包授权弹窗; - 第二个
useEffect里的web3Modal.connect()本质上也会触发相同的钱包授权流程; - 浏览器一次只能处理一个钱包授权弹窗,当两个请求同时触发时,后触发的请求会覆盖或阻塞先触发的那个,导致第一个
useEffect里的异步操作无法正常完成,甚至被浏览器直接忽略。
另外,updateCurrentWalletAddress没有错误处理逻辑,如果用户拒绝授权,后续的setWalletAddress也不会执行,但这不是导致第一个useEffect完全不执行的主因。
修复方案
将两个逻辑合并,避免重复发起钱包授权请求,同时保证流程的顺序性。可以通过Web3Modal获取连接后,直接从中提取钱包地址,无需单独调用eth_requestAccounts:
import React, {createContext, useState, useEffect, useContext} from 'react' import { ethers } from 'ethers' import Web3Modal from 'web3modal' import { ABI, ADDRESS } from '../contract' const GlobalContext = createContext() export const GlobalContextProvider = ({children})=>{ const [walletAddress, setWalletAddress] = useState('') const [provider, setprovider] = useState('') const [contract, setcontract] = useState(null) const [showAlert, setshowAlert] = useState({status: false, type: 'info', message: ''}) const updateCurrentWalletAddress = async (accounts) => { if(accounts) setWalletAddress(accounts[0]) } useEffect(() => { const initWalletAndContract = async () => { // 统一通过Web3Modal获取连接,避免重复授权 const web3Modal = new Web3Modal() const connection = await web3Modal.connect() const newProvider = new ethers.providers.Web3Provider(connection) // 从连接中获取钱包地址 const accounts = await newProvider.listAccounts() updateCurrentWalletAddress(accounts) // 初始化合约 const signer = newProvider.getSigner() const newContract = new ethers.Contract(ADDRESS, ABI, signer) setprovider(newProvider) setcontract(newContract) // 监听账户变化 window.ethereum.on('accountsChanged', updateCurrentWalletAddress) } initWalletAndContract() // 清理监听事件,避免内存泄漏 return () => { window.ethereum.removeListener('accountsChanged', updateCurrentWalletAddress) } }, []) useEffect(() => { if(showAlert?.status){ const timer = setTimeout(()=>{ setshowAlert ({status: false, type: 'info', message: ''}) }, 5000) return ()=> clearTimeout(timer) } }, [showAlert]) return( <GlobalContext.Provider value={{ contract, walletAddress, showAlert, setshowAlert }}> {children} </GlobalContext.Provider> ) } export const useGlobalContext = ()=> useContext(GlobalContext)
修复要点
- 合并逻辑到单个useEffect:避免同时发起多个授权请求,确保流程串行执行;
- 通过Web3Modal连接获取地址:利用
newProvider.listAccounts()从已建立的连接中提取地址,无需单独调用eth_requestAccounts; - 添加清理函数:移除
accountsChanged的监听事件,避免组件卸载后内存泄漏; - 简化冗余代码:去掉重复的授权请求逻辑,统一钱包连接入口。
内容的提问来源于stack exchange,提问作者Arpit Jethava
相关产品推荐
相关产品推荐

