如何避免React Hook重复执行?以usePublicKey为例
解决自定义Hook的单例执行问题
你当前的usePublicKey Hook每次被组件引用时都会重新执行密钥生成流程,原因是useRef创建的fetched是每个Hook实例独立的,无法跨实例共享状态。要实现类似Apollo useQuery的单例执行+缓存效果,可以通过模块级全局变量来维护共享状态和请求锁,具体实现如下:
问题代码分析
你的原代码中,每个usePublicKey实例都有自己的fetched ref,因此每次引用Hook时,!fetched.current都会为true,导致重复执行密钥生成(同时修正了代码中的笔误:k应为keys):
export const usePublicKey = () => { const [publicKey, setPublicKey] = useState() const fetched = useRef(false) // 每个Hook实例独立,无法共享 useEffect(() => { const fetchKey = async () => { const keys = await window.crypto.subtle.generateKey( { name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256', }, true, ['sign', 'verify'] ) const _publicKey = await window.crypto.subtle.exportKey('jwk', keys.publicKey) setPublicKey(_publicKey ) } if(!fetched.current) { fetched.current = true fetchKey() } },[]) return { publicKey } }
单例执行的解决方案
利用模块级全局变量(整个应用生命周期内唯一)来缓存公钥和请求状态,确保密钥生成流程只执行一次,所有Hook实例共享结果:
// 模块级全局缓存:整个应用唯一 let publicKeyCache = null; // 缓存请求Promise,避免并发重复请求 let fetchKeyPromise = null; export const usePublicKey = () => { // 初始化时直接用全局缓存的值 const [publicKey, setPublicKey] = useState(publicKeyCache); useEffect(() => { // 已有缓存,直接更新本地状态 if (publicKeyCache) { setPublicKey(publicKeyCache); return; } // 已有请求在进行,等待同一个Promise完成 if (fetchKeyPromise) { fetchKeyPromise.then(key => setPublicKey(key)); return; } // 首次发起请求,缓存Promise fetchKeyPromise = (async () => { try { const keys = await window.crypto.subtle.generateKey( { name: 'RSASSA-PKCS1-v1_5', modulusLength: 2048, publicExponent: new Uint8Array([1, 0, 1]), hash: 'SHA-256', }, true, ['sign', 'verify'] ); const _publicKey = await window.crypto.subtle.exportKey('jwk', keys.publicKey); // 缓存结果到全局变量 publicKeyCache = _publicKey; return _publicKey; } finally { // 请求完成后清空Promise标记 fetchKeyPromise = null; } })(); // 订阅请求结果,更新本地状态 fetchKeyPromise.then(key => setPublicKey(key)); }, []); return { publicKey }; };
方案说明
- 全局缓存变量:
publicKeyCache存储生成好的公钥,所有Hook实例共享这个值;fetchKeyPromise用于标记当前是否有请求在进行,避免多个Hook同时发起重复请求。 - 并发处理:当多个组件同时调用
usePublicKey时,后续调用会直接等待第一个请求的Promise,不会重复执行密钥生成。 - 状态同步:请求完成后,所有Hook实例都会通过
setPublicKey更新本地状态,拿到最新的公钥。
内容的提问来源于stack exchange,提问作者Elliott de Launay
相关产品推荐
相关产品推荐

