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

React异步加载大数据致Lighthouse性能评分偏低问题排查与优化

问题描述

我们网站在异步useEffect钩子中加载大量包含Base64格式展览图片的展览数据,尽管已经使用lazy和Suspense进行组件懒加载,但Google Lighthouse性能评分依然不理想。想明确该情况导致低分的原因,并获取可行的优化方案。相关代码示例如下:

MainPage组件代码

import React, { Component, Fragment } from 'react';
...

const MeetUs = React.lazy(() => import('./MeetUs.container.js'));

function MainPage(props) {
    return (
        <>
            ...
            <React.Suspense fallback={<div></div>}>
                <MeetUs 
                    key="meetUs" 
                    currentLanguage={props.currentLanguage}
                />
            </React.Suspense>
        </>
    );
}

export default MainPage;

MeetUs组件代码

import React, { Component, useState, useEffect } from 'react';
...
const Exhibition = React.lazy(() => import('./Exhibition.component'));
...

function MeetUs(props) {
    const [exhibitions, setExhibitions] = useState([]);
    const [isLoading, setIsLoading] = useState(true);

    useEffect(() => {
        async function loadExhibitions() {
            return new Promise((resolve, reject) => {
                window.axios.post(window.location.origin + '/company/exhibitions', {
                    requestType: 'getExhibitions'
                })
                .then((response) => {
                    if(response.data?.success) {
                        resolve(response.data.exhibitions);
                    }
                    else {
                        reject({error: 'Not successful'});
                    }
                })
                .catch(function (error) {
                    console.log(error);
                    reject(error);
                });
            });
        }

        loadExhibitions()
        .then((exhibitions) => {
            setExhibitions(exhibitions);
        })
        .then(() => {
            setIsLoading(false);
        })
        .catch((error) => {
            setIsLoading(false);
        });
    },[]);  

    return (
        <div className="container-fluid marginTop50px">
            ...
            {
                exhibitions.map((exhibition, index) => {
                    return (
                        <React.Suspense fallback={<div></div>}>
                            <React.Fragment>
                                <ExhibitionDiv>
                                    <Exhibition/>
                                </ExhibitionDiv>
                            </React.Fragment>
                        </React.Suspense>
                    )
                })
            }
            ...
        </div>
    );
}

export default MeetUs;

低分原因分析
  1. 组件懒加载不覆盖数据加载:React.lazy仅负责拆分组件代码包,不会处理数据加载逻辑。当前数据请求在MeetUs组件挂载后立即通过useEffect发起,大体积的Base64数据会阻塞主线程,直接拉低First Contentful Paint (FCP)、Largest Contentful Paint (LCP)等核心性能指标。
  2. Base64图片体积冗余:Base64编码会使图片体积比原文件增加约30%,大量这类数据一次性加载会拉长网络请求耗时,同时解析Base64的过程也会占用主线程资源,延缓页面渲染。
  3. 无效的Suspense嵌套:在列表循环中给每个Exhibition组件单独套Suspense,既没有关联数据加载逻辑,又增加了React的渲染开销;且fallback为空div,无法提供有效加载状态,也会影响用户体验维度的评分。
  4. 数据加载时机不合理:组件挂载时正处于浏览器关键渲染阶段,此时发起大体积数据请求,会抢占主线程资源,导致页面渲染延迟。

优化方案

1. 替换Base64为图片URL

后端直接返回图片的服务器/CDN地址,而非Base64编码。这样浏览器可单独缓存图片、并行加载,还能通过loading="lazy"实现图片懒加载,大幅减少初始数据体积。

  • 接口返回示例:{ "imageUrl": "/assets/exhibitions/2024-spring.jpg", ... }
  • 组件使用示例:
// Exhibition组件内
<img src={props.data.imageUrl} loading="lazy" alt="展览现场" />

2. 数据分页或虚拟滚动

避免一次性加载所有展览数据,采用分页加载或虚拟滚动方案:

  • 分页加载:给接口添加page和limit参数,在MeetUs中维护页码状态,监听滚动事件触发下一页请求;
  • 虚拟滚动:使用react-window或react-virtualized库,仅渲染当前视口内的展览项,减少DOM节点数量和初始数据量。

3. 数据预加载与缓存

  • 若采用SSR/SSG架构(如Next.js),将数据请求移至服务端(getServerSideProps/getStaticProps),提前注入组件数据,避免客户端加载阻塞;
  • 纯客户端场景下,用localStorage/sessionStorage缓存已加载的展览数据,用户再次访问时直接读取缓存,减少重复请求。

4. 用Suspense处理数据加载(React 18+)

React 18支持用Suspense处理异步数据,可借助react-query等库实现,替代useEffect的异步请求逻辑,避免状态更新导致的重复渲染:

import { useQuery } from '@tanstack/react-query';

function MeetUs(props) {
  const { data: exhibitions, isLoading } = useQuery({
    queryKey: ['exhibitions'],
    queryFn: () => window.axios.post('/company/exhibitions', { requestType: 'getExhibitions' })
      .then(res => res.data.exhibitions)
  });

  if (isLoading) return <div>加载展览信息中...</div>;

  return (
    <div className="container-fluid marginTop50px">
      {exhibitions.map((item, idx) => (
        <ExhibitionDiv key={idx}>
          <Exhibition data={item} />
        </ExhibitionDiv>
      ))}
    </div>
  );
}

5. 优化组件懒加载逻辑

移除列表循环中的嵌套Suspense,将Exhibition的懒加载提升到组件顶部,用单个Suspense包裹所有列表项,减少渲染开销:

const Exhibition = React.lazy(() => import('./Exhibition.component'));

function MeetUs(props) {
  // ...数据逻辑

  return (
    <div className="container-fluid marginTop50px">
      ...
      <React.Suspense fallback={<div>加载展览项...</div>}>
        {exhibitions.map((item, idx) => (
          <ExhibitionDiv key={idx}>
            <Exhibition data={item} />
          </ExhibitionDiv>
        ))}
      </React.Suspense>
      ...
    </div>
  );
}

6. 隔离主线程阻塞(若必须保留Base64)

将Base64解析逻辑移至Web Worker中,避免占用主线程:

// base64-worker.js
self.onmessage = (e) => {
  const [header, data] = e.data.base64.split(',');
  const mime = header.match(/:(.*?);/)[1];
  const byteString = atob(data);
  const uint8Array = new Uint8Array(byteString.length);
  
  for (let i = 0; i < byteString.length; i++) {
    uint8Array[i] = byteString.charCodeAt(i);
  }
  
  const blob = new Blob([uint8Array], { type: mime });
  self.postMessage({ url: URL.createObjectURL(blob) });
};

// MeetUs组件内使用
const handleBase64Image = (base64) => {
  const worker = new Worker('./base64-worker.js');
  worker.postMessage({ base64 });
  worker.onmessage = (e) => {
    // 更新图片URL状态
    setImageUrl(e.data.url);
    worker.terminate();
  };
};

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 00:27:14