如何阻止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;
问题分析与解决方案
核心问题点
- React 18 StrictMode双重执行:开发环境下StrictMode会重复执行
useEffect、函数组件体逻辑,导致请求发起两次。第一次请求可能因URL错误、接口延迟等返回undefined,第二次才成功,造成页面先空白再加载。 - 组件体直接调用
setPieData:每次组件渲染都会触发状态更新,引发无限重渲染;且StatsData未就绪时,PieData的value为undefined,导致饼图渲染空白网格。 - 加载状态设置时机错误:无论请求成功与否都直接关闭加载状态,若第一次请求失败,页面会提前显示空白容器。
具体修复步骤
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
相关产品推荐
相关产品推荐

