React类组件setState设置defaultAccount为MetaMask账户后仍返回null
问题描述
运行React类组件代码尝试连接MetaMask并打印defaultAccount状态时出现异常:
- 即便已执行
setState将其赋值为MetaMask账户地址,打印结果仍显示为null - 输出
defaultAccount时始终返回初始空值 - 控制台可正常打印
accounts[0]的正确账户地址,但setState执行后立即读取this.state.defaultAccount仍为初始null值
问题复现代码
class App extends Component { constructor(){ super() this.state={ defaultAccount:null, provider:null, connectState:false, ethers:null, gameContract:null } this.loadWindow=this.loadWindow.bind(this) } async loadWindow(){ if (window.ethereum){ try{ const accounts =await window.ethereum.request({method: 'eth_requestAccounts',}); console.log(accounts[0]) const contractAbi=[ "function getRandomNumber() public returns(bytes32 requestId)", "function fulfillRandomness(bytes32 requestId,uint randomness) internal override", "function contractBalance() public view returns(uint)" ] const provider= new ethers.providers.Web3Provider(window.ethereum,"any") const signer = await provider.getSigner() const gameContract=new ethers.Contract("0x6cEB2a506b6b1145241BC92D4CFD94aE5588eB18",contractAbi,signer) this.setState({defaultAccount:accounts[0],connectState:true,provider:provider,ethers:ethers,gameContract:gameContract}); console.log(this.state.defaultAccount) // 此处打印仍为null }catch(e){ console.log(e) } window.alert("Wallet Connected..") } else{ window.alert("Metamask Not Found..") } } }
产生原因
这是React类组件setState的异步批量更新机制导致的:
- React为了优化渲染性能,会对同一个执行周期内的多次
setState调用做合并批量处理,调用setState时不会立即同步修改this.state的值,状态更新会被推入更新队列,等当前同步代码执行完毕后才会统一处理更新、触发重渲染 - 你在调用
setState后紧接着同步执行console.log,此时状态更新还未被应用,读取到的自然还是更新前的初始null值 - 注意:即便是在
async函数中,setState本身也不返回Promise,await关键字对它没有等待效果,不会等到状态更新完成再执行后续代码 - 你在
setState之前打印的accounts[0]是MetaMask接口直接返回的原始值,没有经过React状态流转,所以能正常输出正确地址,和后续状态读取的结果不冲突
修复方案
根据实际使用场景任选一种即可:
- 方案1:使用
setState的第二个回调参数,该回调会在状态真正更新完成、组件重渲染后触发,此时读取到的就是最新状态值
this.setState( {defaultAccount:accounts[0],connectState:true,provider:provider,ethers:ethers,gameContract:gameContract}, () => { // 状态更新完成后执行 console.log(this.state.defaultAccount) } );
- 方案2:如果不需要在状态更新后立即执行同步逻辑,只是要确认状态更新结果,可以直接在
render函数中读取状态,或者使用componentDidUpdate生命周期监听状态变化
componentDidUpdate(prevProps, prevState) { // 监听defaultAccount变化 if (prevState.defaultAccount !== this.state.defaultAccount) { console.log("最新账户地址:", this.state.defaultAccount) } }
- 方案3:如果后续逻辑只需要用到账户地址,不需要依赖state中的存储值,可以直接使用已经拿到的
accounts[0]变量,不需要等待state更新完成
内容的提问来源于stack exchange,提问作者Azzor
相关产品推荐
相关产品推荐

