React多异步数据源优先级处理优化首屏渲染方案咨询
问题
在React组件中需要获取外部数据源,其中fetchSlow返回完整数据但速度较慢,fetchFast返回部分数据但速度较快。需求为并行发起两个请求,并遵循以下规则:
- 若
fetchFast先返回,则先渲染组件的部分内容,待fetchSlow返回后再重新渲染为完整数据; - 若
fetchSlow先返回,则忽略fetchFast的结果。
这么做是为了优化首屏渲染时间(fetchSlow有时会非常慢)。
目前已实现的方案通过变量allDataFetched控制是否忽略数据更新,代码如下:
const MyComponent = () => { const [loading, setLoading] = useState(true); const [data, setData] = useState({ data1: null, data2: null }); useEffect(()=>{ let allDataFetched = false; // fetchFast 速度快但仅返回 data1 fetchFast().then((res) => { if (allDataFetched === false) { const tmpData = { ...data, ...result.data }; setData(tmpData); setLoading(false); } }); // fetchSlow 速度慢但返回 data1 和 data2 fetchSlow().then((res) => { setData(res.data); setLoading(false); allDataFetched = true; })},[]); if (!loading) { return ( <div> <div>{data.data1 === null ? "loading" : data.data1}</div> <div>{data.data2 === null ? "loading" : data.data2}</div> </div> ); } return <p>Load</p>; };
但觉得该方案不够理想,希望了解更优的实现方式。
更优实现方案
方案1:用AbortController终止低优先级请求
当fetchSlow先返回时,直接终止fetchFast的请求(如果还未完成),从根源上避免无效的状态更新,比单纯忽略结果更高效。
const MyComponent = () => { const [data, setData] = useState({ data1: null, data2: null }); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const abortController = new AbortController(); const signal = abortController.signal; // 发起快速请求 fetchFast({ signal }) .then(res => { // 仅当请求未被中止时更新状态 if (!signal.aborted) { setData(prev => ({ ...prev, ...res.data })); setIsLoading(false); } }) .catch(err => { // 捕获中止错误,无需额外处理 if (err.name !== 'AbortError') throw err; }); // 发起慢速请求 fetchSlow() .then(res => { setData(res.data); setIsLoading(false); // 中止快速请求,避免后续无效操作 abortController.abort(); }); // 组件卸载时中止所有请求,防止内存泄漏 return () => abortController.abort(); }, []); return isLoading ? ( <p>Load</p> ) : ( <div> <div>{data.data1 ?? "loading"}</div> <div>{data.data2 ?? "loading"}</div> </div> ); };
注意:需要确保fetchFast支持接收signal参数(比如原生fetch、axios都支持该配置)。
方案2:用状态变量跟踪完整数据加载状态
把原方案中的局部变量allDataFetched改成组件状态,让React统一跟踪状态变化,避免闭包陷阱(原代码中data的闭包可能导致状态更新不及时)。
const MyComponent = () => { const [data, setData] = useState({ data1: null, data2: null }); const [isLoading, setIsLoading] = useState(true); const [isFullDataLoaded, setIsFullDataLoaded] = useState(false); useEffect(() => { fetchFast() .then(res => { // 仅当完整数据未加载时更新状态 if (!isFullDataLoaded) { setData(prev => ({ ...prev, ...res.data })); setIsLoading(false); } }); fetchSlow() .then(res => { setData(res.data); setIsLoading(false); setIsFullDataLoaded(true); }); }, [isFullDataLoaded]); return isLoading ? ( <p>Load</p> ) : ( <div> <div>{data.data1 ?? "loading"}</div> <div>{data.data2 ?? "loading"}</div> </div> ); };
说明:用React状态替代局部变量后,逻辑更符合React的状态管理思维,可读性和可维护性更强。
方案3:封装请求逻辑解耦组件代码
将请求优先级处理逻辑封装成独立函数,让组件只负责状态更新和渲染,便于复用和单独测试。
const fetchWithPriority = () => { let isFullDataReceived = false; const fastPromise = fetchFast(); const slowPromise = fetchSlow(); return { getUpdates: async function*(emit) { // 监听两个请求的结果 const promises = [ fastPromise.then(res => ({ type: 'partial', data: res.data })), slowPromise.then(res => ({ type: 'full', data: res.data })) ]; for await (const result of promises) { if (result.type === 'full') { isFullDataReceived = true; emit(result.data, false); break; // 完整数据返回后,终止后续处理 } if (!isFullDataReceived) { emit(result.data, false); } } } }; }; const MyComponent = () => { const [data, setData] = useState({ data1: null, data2: null }); const [isLoading, setIsLoading] = useState(true); useEffect(() => { const { getUpdates } = fetchWithPriority(); const updateIterator = getUpdates((newData, loading) => { setData(prev => ({ ...prev, ...newData })); setIsLoading(loading); }); const runUpdates = async () => { await updateIterator.next(); }; runUpdates(); }, []); return isLoading ? ( <p>Load</p> ) : ( <div> <div>{data.data1 ?? "loading"}</div> <div>{data.data2 ?? "loading"}</div> </div> ); };
说明:异步迭代器的方式让请求逻辑和组件渲染彻底解耦,后续修改请求规则时无需改动组件代码。
内容的提问来源于stack exchange,提问作者Thomas
相关产品推荐
相关产品推荐

