React严格模式下Zustand加载状态异常问题及最优方案问询
针对Zustand + React严格模式的加载状态优化方案
1. 用请求标识跟踪有效请求
给每个请求生成唯一ID,仅当当前请求是最新的那个时,才允许更新loading状态,旧请求的状态变更会被直接忽略,避免干扰后续请求的状态展示。
示例代码:
catsStore.ts
import { create } from 'zustand'; type Cat = { id: string; name: string }; type CatsState = { cats: Cat[]; loading: boolean; latestRequestId: string | null; }; export const useCatsStore = create<CatsState>((set) => ({ cats: [], loading: false, latestRequestId: null, getCats: async () => { const requestId = crypto.randomUUID(); set({ loading: true, latestRequestId: requestId }); try { const controller = new AbortController(); const res = await fetch('/api/cats', { signal: controller.signal }); const cats = await res.json(); // 仅最新请求能更新状态 set((state) => { if (state.latestRequestId === requestId) { return { cats, loading: false }; } return {}; }); } catch (err) { // 忽略AbortError,且仅最新请求能修改loading if (!(err instanceof DOMException && err.name === 'AbortError')) { set((state) => { if (state.latestRequestId === requestId) { return { loading: false }; } return {}; }); } } }, }));
2. 组件层结合useEffect清理旧请求
利用React useEffect的清理函数,在组件重新渲染时主动取消前一次请求,同时让Zustand只响应未被取消的请求状态更新。
Cats.tsx
import { useCatsStore } from './catsStore'; import { useEffect } from 'react'; export const Cats = () => { const { cats, loading, getCats } = useCatsStore(); useEffect(() => { const controller = new AbortController(); const fetchCats = async () => { try { await getCats(controller.signal); } catch (err) { if (!(err instanceof DOMException && err.name === 'AbortError')) { console.error('Failed to fetch cats:', err); } } }; fetchCats(); return () => controller.abort(); }, [getCats]); return ( <div> {loading ? <p>Loading...</p> : ( <ul> {cats.map(cat => <li key={cat.id}>{cat.name}</li>)} </ul> )} </div> ); };
对应的catsStore中更新getCats方法:
// catsStore.ts中修改getCats getCats: async (signal: AbortSignal) => { const requestId = crypto.randomUUID(); set({ loading: true, latestRequestId: requestId }); try { const res = await fetch('/api/cats', { signal }); const cats = await res.json(); set((state) => state.latestRequestId === requestId ? { cats, loading: false } : {}); } catch (err) { if (!(err instanceof DOMException && err.name === 'AbortError')) { set((state) => state.latestRequestId === requestId ? { loading: false } : {}); } } },
3. 封装通用加载状态管理Hook
把请求和状态跟踪逻辑抽成通用Hook,统一处理严格模式下的重复渲染问题,避免重复代码。
示例通用Hook:
import { useState, useCallback } from 'react'; export const useFetchWithLoading = <T>(fetchFn: (signal: AbortSignal) => Promise<T>) => { const [loading, setLoading] = useState(false); const [data, setData] = useState<T | null>(null); const [error, setError] = useState<Error | null>(null); const [latestRequestId, setLatestRequestId] = useState<string | null>(null); const execute = useCallback(async () => { const requestId = crypto.randomUUID(); setLatestRequestId(requestId); setLoading(true); setError(null); const controller = new AbortController(); try { const result = await fetchFn(controller.signal); if (requestId === latestRequestId) { setData(result); setLoading(false); } } catch (err) { if (!(err instanceof DOMException && err.name === 'AbortError')) { if (requestId === latestRequestId) { setError(err as Error); setLoading(false); } } } return () => controller.abort(); }, [fetchFn, latestRequestId]); return { loading, data, error, execute }; };
在组件中直接调用该Hook,配合Zustand使用即可简化状态管理。
4. 加载状态前置判断避免重复请求
在Zustand的请求方法中,先检查当前是否处于加载状态,如果已经在加载则直接返回,避免重复发起请求。
// catsStore.ts中修改getCats getCats: async () => { // 已在加载则跳过 const currentState = useCatsStore.getState(); if (currentState.loading) return; set({ loading: true }); try { const res = await fetch('/api/cats'); const cats = await res.json(); set({ cats, loading: false }); } catch (err) { if (!(err instanceof DOMException && err.name === 'AbortError')) { set({ loading: false }); } } },
这种方式简单直接,适合单一请求的场景,无需额外生成请求ID。
内容的提问来源于stack exchange,提问作者Anton
相关产品推荐
相关产品推荐

