You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.12 20:42:13