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

React渲染前预加载数据方案咨询及渲染次数优化疑问

React预加载数据实现优化与渲染次数减少方案

一、当前实现的问题与更佳实践

当前实现的潜在问题

  1. 竞态风险:若用户快速点击按钮,preloadedData未加载完成时执行setData(preloadedData),会导致界面展示空对象或旧数据。
  2. 无错误处理:fetch请求未处理API报错、网络中断等失败场景,易引发应用无响应或控制台报错。
  3. 耦合性高:fetchData直接接收setState函数,将数据请求与状态更新强耦合,不利于维护和扩展。
  4. 重复请求隐患:多次快速点击按钮可能触发重复预加载请求,造成资源浪费。

更佳实践

1. 重构请求函数,解耦数据与状态

将fetchData改为返回Promise的独立函数,让组件自主控制状态更新:

async function fetchData(count) {
  const res = await fetch(`https://swapi.dev/api/people/${count}`);
  if (!res.ok) throw new Error('Failed to fetch character');
  const data = await res.json();
  return {
    id: count,
    name: data.name,
    birth_year: data.birth_year,
    gender: data.gender
  };
}

2. 用useRef跟踪预加载状态,避免竞态与重复请求

通过useRef存储当前预加载的ID,确保请求完成后仅更新有效状态:

function App() {
  const [currentData, setCurrentData] = React.useState(null);
  const [preloadedData, setPreloadedData] = React.useState(null);
  const [currentId, setCurrentId] = React.useState(1);
  const preloadingIdRef = React.useRef(null);

  React.useEffect(() => {
    // 初始化加载首个角色并预加载下一个
    if (currentId === 1) {
      const loadInitial = async () => {
        try {
          const firstData = await fetchData(currentId);
          setCurrentData(firstData);
          
          preloadingIdRef.current = currentId + 1;
          const nextData = await fetchData(currentId + 1);
          if (preloadingIdRef.current === currentId + 1) {
            setPreloadedData(nextData);
          }
        } catch (err) {
          console.error('Initial load failed:', err);
        }
      };
      loadInitial();
      return;
    }

    // 切换角色时复用预加载数据,再预加载下一个
    if (preloadedData?.id === currentId) {
      setCurrentData(preloadedData);
      const nextId = currentId + 1;
      
      if (preloadingIdRef.current !== nextId) {
        preloadingIdRef.current = nextId;
        fetchData(nextId)
          .then(data => {
            if (preloadingIdRef.current === nextId) {
              setPreloadedData(data);
            }
          })
          .catch(err => console.error('Preload failed:', err));
      }
    }
  }, [currentId, preloadedData]);

  const handleNext = () => {
    if (preloadedData) {
      setCurrentId(preloadedData.id);
    } else {
      console.log('Next character not ready yet');
    }
  };

  if (!currentData) return <div>Loading...</div>;

  return (
    <main>
      <h1>Starwars character data</h1>
      <p><span>Number:</span> {currentData.id}</p>
      <p><span>Name:</span> {currentData.name}</p>
      <p><span>Birth year:</span> {currentData.birth_year}</p>
      <p><span>Gender:</span> {currentData.gender}</p>
      <button onClick={handleNext} disabled={!preloadedData}>Next character</button>
    </main>        
  );
}

3. 完善用户体验:加载状态与错误提示

  • 初始加载时显示加载占位符
  • 预加载未完成时禁用"Next"按钮
  • 请求失败时展示错误信息并提供重试入口

二、减少渲染次数的方案

三次渲染的原因

每次点击按钮触发三次渲染:

  1. setCount(count + 1)更新count,触发第一次渲染
  2. useEffect中setData(preloadedData)更新data,触发第二次渲染
  3. 预加载请求完成后setPreloadedData更新预加载数据,触发第三次渲染

优化方案

1. 合并状态,减少独立更新次数

将currentData、preloadedData、currentId合并为单个状态对象,减少状态更新触发的渲染:

const [state, setState] = React.useState({
  currentData: null,
  preloadedData: null,
  currentId: 1
});

使用函数式更新确保获取最新状态:

setState(prev => ({
  ...prev,
  currentData: prev.preloadedData,
  currentId: prev.preloadedData.id
}));

2. 避免不必要的状态更新

设置preloadedData前,判断新数据是否与当前值一致,避免重复更新:

if (preloadedData?.id !== nextId) {
  setPreloadedData(data);
}

3. 用React.memo优化子组件(若拆分组件)

将角色信息展示部分拆分为独立组件,用React.memo包裹避免不必要重渲染:

const CharacterCard = React.memo(({ data }) => {
  return (
    <div>
      <p><span>Number:</span> {data.id}</p>
      <p><span>Name:</span> {data.name}</p>
      <p><span>Birth year:</span> {data.birth_year}</p>
      <p><span>Gender:</span> {data.gender}</p>
    </div>
  );
});

4. 优化useEffect依赖项

用useCallback包裹fetchData,防止每次渲染创建新函数导致useEffect重复执行:

const fetchData = React.useCallback(async (count) => {
  const res = await fetch(`https://swapi.dev/api/people/${count}`);
  if (!res.ok) throw new Error('Failed to fetch character');
  const data = await res.json();
  return {
    id: count,
    name: data.name,
    birth_year: data.birth_year,
    gender: data.gender
  };
}, []);

内容的提问来源于stack exchange,提问作者aabb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.06 10:45:19