React Suspense最佳实践优化:组件内数据获取与状态存储需求
React Suspense 数据获取方案改进建议
问题背景
我正在为React Web应用添加进度指示器,基于React Suspense数据获取的实现方案完成了初始代码,但存在两个问题:
- 返回的数据未存入组件状态,无法实现数据过滤等交互功能
- suspender需声明在组件外部,不符合开发习惯
期望将组件改写为内部包含suspender和state的形式,寻求改进建议。
初始实现代码
export interface ISuspender<T> { read(): T; } const createSuspender = <T>(promise: Promise<T>): ISuspender<T> => { let status: string = 'pending'; let response: T; const suspender: Promise<void> = promise.then( (result) => { status = "susccess"; response = result; }, (error) => { status = "error"; response = error; } ); const read = (): T => { switch (status) { case 'pending': throw suspender; case 'error': throw response; default: return response; } } return { read } } const getSetsSuspender: ISuspender<Scry.Set[]> = createSuspender(Scry.Sets.all()); export const MtgSetList: React.FunctionComponent = () => { const sets: Scry.Set[] = getSetsSuspender.read(); return ( <SetList sets={sets} /> ) }
期望的组件结构
export const MtgSetList: React.FunctionComponent = () => { const getSetsSuspender: ISuspender<Scry.Set[]> = createSuspender(Scry.Sets.all()); const [sets, setSets] = React.useState<Scry.Set[]>(); useEffect(() => { setSets(getSetsSuspender.read()); }, [getSetsSuspender]); return ( <SetList sets={sets} /> ) }
改进方案
方案一:保留Suspense机制,同时将数据存入State
该方案既利用Suspense的进度指示器能力,又将数据存入组件State支持交互,且suspender逻辑完全在组件内部:
import React, { useState, useRef } from 'react'; export interface ISuspender<T> { read(): T; } const createSuspender = <T>(promise: Promise<T>): ISuspender<T> => { // 修正拼写错误:susccess -> success,同时用联合类型约束status let status: 'pending' | 'success' | 'error' = 'pending'; let response: T; const suspender: Promise<void> = promise.then( (result) => { status = "success"; response = result; }, (error) => { status = "error"; response = error as unknown as T; } ); const read = (): T => { switch (status) { case 'pending': throw suspender; case 'error': throw response; default: return response; } }; return { read }; }; export const MtgSetList: React.FunctionComponent = () => { const [sets, setSets] = useState<Scry.Set[] | undefined>(undefined); // 用useRef缓存suspender,避免组件重渲染时重复发起请求 const suspenderRef = useRef<ISuspender<Scry.Set[]> | null>(null); // 仅在组件首次渲染时创建suspender if (!suspenderRef.current) { suspenderRef.current = createSuspender(Scry.Sets.all()); } // 渲染阶段调用read(),触发Suspense的加载状态(需父组件用<Suspense fallback={...}>包裹) const fetchedSets = suspenderRef.current.read(); // 数据就绪后更新State,支持后续交互操作 if (fetchedSets && sets !== fetchedSets) { setSets(fetchedSets); } return <SetList sets={sets} />; };
关键说明:
- 用
useRef缓存suspender,确保组件重新渲染时不会重复发起API请求 - 在渲染阶段调用
read(),符合Suspense的工作机制,触发进度指示器(父组件需包裹<Suspense fallback={<你的进度指示器组件 />}>) - 数据获取成功后同步到State,可基于State实现过滤、排序等交互
- 错误处理需配合React的
ErrorBoundary组件,捕获read()抛出的错误
方案二:改用传统useEffect+State方案(放弃Suspense)
如果更习惯传统的异步数据获取模式,可直接用useEffect管理请求,同时维护加载、错误、数据状态,完全不需要Suspender:
import React, { useState, useEffect } from 'react'; export const MtgSetList: React.FunctionComponent = () => { const [sets, setSets] = useState<Scry.Set[] | undefined>(undefined); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState<Error | null>(null); useEffect(() => { const fetchSets = async () => { try { const fetchedSets = await Scry.Sets.all(); setSets(fetchedSets); } catch (err) { setError(err as Error); } finally { setIsLoading(false); } }; fetchSets(); }, []); // 手动处理加载、错误状态 if (isLoading) return <你的进度指示器组件 />; if (error) return <div>加载失败:{error.message}</div>; return <SetList sets={sets} />; };
关键说明:
- 完全在组件内部处理请求逻辑,符合你的开发习惯
- 手动管理加载、错误、数据状态,无需依赖Suspense机制
- 数据直接存入State,可直接实现过滤等交互功能
内容的提问来源于stack exchange,提问作者Richard Eagle
相关产品推荐
相关产品推荐

