React自定义useWallet Hook中signer跨组件调用时重置为undefined问题
问题分析与解决方案
你的问题核心在于每次调用useWallet自定义Hook时,都会创建独立的useRef和provider实例。Home组件和MyChildComponent各自调用useWallet,它们的signer状态完全隔离——Home中通过connect赋值的signer只属于它自己的Hook实例,子组件的Hook里signer自然还是undefined。
要解决跨组件共享钱包状态的问题,需要用React Context来统一管理钱包相关的状态和方法,确保所有组件使用的是同一个状态实例。
具体实现步骤
1. 创建钱包Context与Provider
新建WalletContext.tsx文件,封装钱包状态和逻辑:
import { createContext, useContext, useState, useMemo } from 'react'; import { ethers, InterfaceAbi, parseEther } from "ethers"; // 定义Context类型 type WalletContextType = { signer: ethers.JsonRpcSigner | undefined; connect: () => Promise<void>; deployContract: (abi: InterfaceAbi, bytecode: string) => Promise<void>; }; // 创建Context,默认值为undefined const WalletContext = createContext<WalletContextType | undefined>(undefined); // 定义Provider组件,统一管理钱包状态 export const WalletProvider = ({ children }: { children: React.ReactNode }) => { // 用useState存储signer,确保状态更新能触发组件重渲染 const [signer, setSigner] = useState<ethers.JsonRpcSigner | undefined>(undefined); // 用useMemo缓存provider,避免每次渲染重复创建 const provider = useMemo(() => new ethers.BrowserProvider(window.ethereum), []); const connect = async () => { const signerInstance = await provider.getSigner(); setSigner(signerInstance); console.log(signerInstance); }; const deployContract = async (abi: InterfaceAbi, bytecode: string) => { if (!signer) { throw new Error("Signer not connected"); } const factory = new ethers.ContractFactory(abi, bytecode, signer); await factory.deploy(parseEther("0")); }; return ( <WalletContext.Provider value={{ signer, connect, deployContract }}> {children} </WalletContext.Provider> ); }; // 封装自定义Hook,简化Context调用 export const useWallet = () => { const context = useContext(WalletContext); if (!context) { throw new Error('useWallet must be used within a WalletProvider'); } return context; };
2. 在根组件中包裹Provider
修改你的根组件(如App.tsx),用WalletProvider包裹所有需要使用钱包功能的组件:
import { WalletProvider } from './WalletContext'; import Home from './Home'; function App() { return ( <WalletProvider> <Home /> </WalletProvider> ); } export default App;
3. 修改Home组件使用统一的Hook
import { useEffect } from 'react'; import { useWallet } from './WalletContext'; import MyChildComponent from './MyChildComponent'; export default function Home() { const { connect } = useWallet(); useEffect(() => { connect().catch((err) => { console.log(err); }); }, [connect]); // 依赖connect,确保函数更新时重新执行 return <MyChildComponent />; }
4. 在子组件中调用deployContract
import { useWallet } from './WalletContext'; export default function MyChildComponent() { const { deployContract } = useWallet(); const handleDeploy = async () => { // 替换为你的合约ABI和字节码 const contractAbi = []; const contractBytecode = ""; try { await deployContract(contractAbi, contractBytecode); console.log("合约部署成功"); } catch (err) { console.error("部署失败:", err); } }; return ( <button onClick={handleDeploy}>部署智能合约</button> ); }
关键优化点
- 使用
useState替代useRef存储signer:useState的状态更新会触发组件重渲染,确保子组件能同步拿到最新的signer。 - 用
useMemo缓存provider:避免组件每次渲染都创建新的BrowserProvider实例,提升性能。 - 通过Context共享状态:所有调用
useWallet的组件都会拿到同一个状态实例,解决了状态隔离的问题。
内容的提问来源于stack exchange,提问作者Adri HM
相关产品推荐
相关产品推荐

