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

如何阻止React 18 StrictMode下页面重复渲染及空白问题

React 18 StrictMode下路由跳转页面空白、数据延迟加载问题

我正在使用React 18 StrictMode,通过Router跳转页面时,页面会先空白2-3秒,之后才加载数据展示。比如Dashboard页面的饼图,跳转后先显示空白网格,过几秒才加载出饼图。我试过用state做加载动画、useRef检查、Suspense Callback这些方法,都没用。

注:在useEffect里加日志后发现,跳转页面时首次请求返回undefined,几秒后再次请求才成功拿到数据。希望页面只渲染一次,解决初始空白问题。


示例代码

Service.js

import axios from 'axios';
//demo code
class Service {

  async fetchData1() {
    try {
      const response = await axios.get("https//some-end-point");
      return response.data;
    } catch (error) {
      console.log('Error retrieving data:'+ error);
    }
  }

  async fetchData2(){
      try {
        const response = await axios.get("https//some-end-point");
        return response.data;
      } catch (error) {
        console.log('Error retrieving data:'+ error);
      }
  }

}

export default new Service();

Dashboard.js

import React, { useEffect, useState } from 'react';
import Service from '../services/Service';

const Dashboard = () => {
  const [BarData, setBarData] = useState([]);
  const [StatsData, setStatsData] = useState([]);
  const [PieData, setPieData] = useState([]);
  const [isLoading, setIsLoading] = useState(true);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const data1 = await Service.fetchData1();
        const data2 = await Service.fetchData2();
        if (data1) setBarData(data1);
        if (data2) setStatsData(data2);
      } catch (error) {
        console.log('Error fetching data:' + error);
      }
      setIsLoading(false);
    };

    fetchData();
  }, []);

  setPieData([
    {
      label: 'Label A',
      value: StatsData['Total Data A'],
    },
    {
      label: 'Label B',
      value: StatsData['Total Data B'],
    },
  ]);

  const PieDataChart = {
    series: PieData.map(item => item.value),
    options: {
      chart: {
        type: 'pie',
        width: 300,
        height: 300,
      },
      dataLabels: {
        enabled: false,
      },
      labels: PieData.map(item => item.label),
      //some properties
    },
  };

  if (isLoading) {
    return (
      <div className="loader">
        <RingLoader color="#36D7B7" loading={true} size={100} />
      </div>
    );
  }

  return (
    <div className="page-container">
      {/* demo data*/}
      <div className="pieChart-Wrapper">
        <div className="pieChart">
          <ReactApexChart options={PieDataChart.options} series={PieDataChart.series} type="pie" />
        </div>
      </div>
    </div>
  );
};

export default Dashboard;

问题分析与解决方案

核心问题点

  1. React 18 StrictMode双重执行:开发环境下StrictMode会重复执行useEffect、函数组件体逻辑,导致请求发起两次。第一次请求可能因URL错误、接口延迟等返回undefined,第二次才成功,造成页面先空白再加载。
  2. 组件体直接调用setPieData:每次组件渲染都会触发状态更新,引发无限重渲染;且StatsData未就绪时,PieData的value为undefined,导致饼图渲染空白网格。
  3. 加载状态设置时机错误:无论请求成功与否都直接关闭加载状态,若第一次请求失败,页面会提前显示空白容器。

具体修复步骤

1. 修复Service层错误处理与URL格式

原代码URL缺少冒号,且请求失败时无返回值,导致调用方拿到undefined:

// Service.js 修改后
async fetchData1() {
  try {
    const response = await axios.get("https://some-end-point"); // 修复URL格式
    return response.data;
  } catch (error) {
    console.log('Error retrieving data:'+ error);
    throw error; // 抛出错误让上层捕获处理
  }
}

async fetchData2(){
    try {
      const response = await axios.get("https://some-end-point"); // 修复URL格式
      return response.data;
    } catch (error) {
      console.log('Error retrieving data:'+ error);
      throw error;
    }
}

2. 用useMemo推导PieData,移除组件体的setPieData

直接调用setPieData会引发无限重渲染,改用useMemo根据StatsData推导数据:

// Dashboard.js 修改相关部分
import React, { useEffect, useState, useMemo } from 'react'; // 引入useMemo

// ... 省略其他代码 ...

// 替换原setPieData为useMemo
const PieData = useMemo(() => {
  if (!StatsData['Total Data A'] || !StatsData['Total Data B']) {
    return []; // 数据未就绪时返回空数组,避免饼图报错
  }
  return [
    {
      label: 'Label A',
      value: StatsData['Total Data A'],
    },
    {
      label: 'Label B',
      value: StatsData['Total Data B'],
    },
  ];
}, [StatsData]); // 仅当StatsData变化时重新计算

const PieDataChart = useMemo(() => ({
  series: PieData.map(item => item.value),
  options: {
    chart: {
      type: 'pie',
      width: 300,
      height: 300,
    },
    dataLabels: {
      enabled: false,
    },
    labels: PieData.map(item => item.label),
    //some properties
  },
}), [PieData]); // 依赖PieData,避免不必要的对象创建

3. 修正useEffect的加载状态与重复请求问题

用useRef标记请求是否已完成,避免StrictMode下的重复请求;仅在数据成功获取后关闭加载状态:

// Dashboard.js 修改useEffect部分
import React, { useEffect, useState, useMemo, useRef } from 'react'; // 引入useRef

const Dashboard = () => {
  // ... 省略其他state定义 ...
  const isFetched = useRef(false); // 标记是否已完成请求

  useEffect(() => {
    if (isFetched.current) return; // StrictMode第二次执行时直接跳过
    isFetched.current = true;

    const fetchData = async () => {
      try {
        // 并行请求提升速度,替代串行await
        const [data1, data2] = await Promise.all([
          Service.fetchData1(),
          Service.fetchData2()
        ]);
        if (data1) setBarData(data1);
        if (data2) setStatsData(data2);
        setIsLoading(false); // 数据成功获取后关闭加载
      } catch (error) {
        console.log('Error fetching data:' + error);
        // 可添加错误状态,显示错误提示
        setIsLoading(false);
      }
    };

    fetchData();
  }, []);

4. 优化渲染逻辑,添加兜底显示

确保加载状态生效,数据为空时显示兜底内容:

// Dashboard.js 修改返回部分
return (
  <div className="page-container">
    {isLoading ? (
      <div className="loader">
        <RingLoader color="#36D7B7" loading={true} size={100} />
      </div>
    ) : (
      <div className="pieChart-Wrapper">
        <div className="pieChart">
          {PieData.length > 0 ? (
            <ReactApexChart options={PieDataChart.options} series={PieDataChart.series} type="pie" />
          ) : (
            <div>暂无数据</div> // 数据为空时的兜底显示
          )}
        </div>
      </div>
    )}
  </div>
);

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 04:07:04