Zustand多异步函数共用loading状态引发无限重渲染问题
Zustand共享loading状态引发无限重渲染与永久加载问题
问题场景
使用Zustand 4.1.5的React应用中,进入FairDetails页面的LogDetails标签页时,两个共用同一loading状态的异步函数并发执行,导致:
- 应用无限重渲染
- index.js中的
<ComponentSpinner />永久显示,但实际loading状态为false
相关代码
Zustand Store
const initialState = { selectedFair: {}, params: {}, fairLogDetails: {}, loading: false, error: null } const useFairStore = create((set, get) => ({ ...initialState, getFairs: async (params) => { const endpoint = params?.role === 'admin' ? '/fair/admin' : '/fair/all' set({ loading: true }) try { const { data } = await axios(`${endpoint}?page=${params?.page || 1}&limit=${params?.limit || 10}`) set({ fairs: data?.data, totalFairs: data?.total, params, error: null }) } catch (e) { set({ error: e }) } finally { set({ loading: false }) } }, getFairLogDetails: async (params, reqBody = {}) => { set({ loading: true }) try { const { fairId } = params const response = await axios.post(`/report/fair-statistics/${fairId}`, reqBody) set({ fairLogDetails: response?.data, error: null }) } catch (e) { set({ error: e }) return Promise.reject({ code: e?.response?.status, message: e?.response?.data?.message }) } finally { set({ loading: false }) } }, }))
FairDetails/index.js
const { fairId } = useParams() const [selectedFair, getFair, loading, cleanStore] = useFairStore( (state) => [state.selectedFair, state.getFair, state.loading, state.cleanStore], shallow ) useEffect(() => { getFair(fairId) return () => cleanStore() }, [fairId]) ... if (loading) return <ComponentSpinner /> return ( // 包含LogDetails组件的内容 )
LogDetails.js
const [selectedFair, getFairLogDetails, fairLogDetails] = useFairStore( (state) => [state.selectedFair, state.getFairLogDetails, state.fairLogDetails], shallow ) useEffect(() => { getFairLogDetails({fairId: selectedFair?._id}) }, [selectedFair?._id]) return ( // 日志详情内容 )
原因分析
- 共享loading状态的并发冲突:两个异步函数共用同一个
loading字段,并发执行时,第一个函数的finally块把loading设为false,但第二个函数仍在执行,会立刻把loading重新设为true,导致loading状态在true和false之间快速切换,触发组件频繁重渲染。 - 组件渲染循环触发:index.js中当
loading为true时返回Spinner,状态切换会导致Spinner和子组件反复卸载/挂载;子组件LogDetails挂载后又会触发getFairLogDetails,再次修改loading状态,形成循环。 - selectedFair引用变化触发重复请求:
getFair更新selectedFair时,每次都会生成新的对象引用,即使内容没有变化,shallow比较会认为状态更新,触发LogDetails的useEffect重新执行,再次调用getFairLogDetails,加剧循环。
解决方案
1. 拆分独立的loading状态
为每个异步操作单独设置loading字段,避免互相干扰:
const initialState = { selectedFair: {}, params: {}, fairLogDetails: {}, getFairLoading: false, // 单独的getFair加载状态 getFairLogDetailsLoading: false, // 单独的日志详情加载状态 error: null } const useFairStore = create((set, get) => ({ ...initialState, getFairs: async (params) => { const endpoint = params?.role === 'admin' ? '/fair/admin' : '/fair/all' set({ getFairLoading: true }) try { const { data } = await axios(`${endpoint}?page=${params?.page || 1}&limit=${params?.limit || 10}`) // 使用函数式更新,避免覆盖其他状态 set(state => ({ ...state, fairs: data?.data, totalFairs: data?.total, params, error: null })) } catch (e) { set({ error: e }) } finally { set({ getFairLoading: false }) } }, getFairLogDetails: async (params, reqBody = {}) => { set({ getFairLogDetailsLoading: true }) try { const { fairId } = params const response = await axios.post(`/report/fair-statistics/${fairId}`, reqBody) set(state => ({ ...state, fairLogDetails: response?.data, error: null })) } catch (e) { set({ error: e }) return Promise.reject({ code: e?.response?.status, message: e?.response?.data?.message }) } finally { set({ getFairLogDetailsLoading: false }) } }, // 修正cleanStore,重置所有状态 cleanStore: () => set(initialState) }))
2. 组件中使用对应loading状态
FairDetails/index.js修改
const { fairId } = useParams() const [selectedFair, getFair, getFairLoading, cleanStore] = useFairStore( (state) => [state.selectedFair, state.getFair, state.getFairLoading, state.cleanStore], shallow ) useEffect(() => { getFair(fairId) return () => cleanStore() }, [fairId]) ... if (getFairLoading) return <ComponentSpinner /> return ( // 包含LogDetails组件的内容 )
LogDetails.js修改
const [selectedFair, getFairLogDetails, fairLogDetails, getFairLogDetailsLoading] = useFairStore( (state) => [state.selectedFair, state.getFairLogDetails, state.fairLogDetails, state.getFairLogDetailsLoading], shallow ) // 增加条件判断,避免重复请求 useEffect(() => { if (!selectedFair?._id || Object.keys(fairLogDetails).length > 0) return getFairLogDetails({fairId: selectedFair._id}) }, [selectedFair?._id, fairLogDetails]) return ( // 可根据getFairLogDetailsLoading添加局部加载状态 <div> {getFairLogDetailsLoading && <small>加载中...</small>} {/* 日志详情内容 */} </div> )
3. 优化selectedFair的引用稳定性(可选)
如果getFair返回的selectedFair内容不变时不需要更新引用,可以在set的时候做浅比较:
// 在getFairs的try块中 set(state => { if (shallowEqual(state.selectedFair, data?.data)) { return state // 内容不变则不更新,保持引用稳定 } return { ...state, selectedFair: data?.data, // 其他状态更新 } })
这里需要引入shallowEqual工具(可自行实现或从react-redux等库引入)。
内容的提问来源于stack exchange,提问作者czechaj
相关产品推荐
相关产品推荐

