React首頁(yè)加載速度優(yōu)化小結(jié)
背景
本文將以一個(gè)什么優(yōu)化都沒(méi)有的加載比較慢的React項(xiàng)目為例,一步一步探索怎樣提升React首次打開(kāi)項(xiàng)目首頁(yè)時(shí)加載特別慢的問(wèn)題。
問(wèn)題分析
首先用網(wǎng)頁(yè)控制臺(tái)查看了一下網(wǎng)頁(yè)加載的進(jìn)度,定位到了加載速度很慢的地方

發(fā)現(xiàn)下載網(wǎng)頁(yè)的js文件,有15mb大小,用了8秒,然后看了下打包好的build文件夾,有19.1mb,并且沒(méi)有分包,是一個(gè)js文件。
問(wèn)題探索
代碼精簡(jiǎn)自查
我首先想到的是刪除每個(gè)js文件中沒(méi)有使用到的導(dǎo)入,簡(jiǎn)化一下文件依賴結(jié)構(gòu),再刪除一些不需要的代碼,清理一下用不到的圖片素材之類的。
刪除這個(gè)的時(shí)候,我沒(méi)找到什么好辦法,我使用的是vscode自帶的功能,自動(dòng)整理所有未使用的import。
使用方法:
打開(kāi)左上角,文件-首選項(xiàng)-設(shè)置,輸入“setting”,選擇“字體”,點(diǎn)擊“在settings.json中編輯”,在json文件中輸入
"editor.codeActionsOnSave": { "source.organizeImports": true },
輸入這個(gè)之后,每個(gè)js文件,在保存時(shí)會(huì)自動(dòng)去除沒(méi)有使用的import。
全部刪除之后,build完,小了一點(diǎn),但依然還有17.7mb,這很顯然還是不滿足要求。
刪除沒(méi)用的包
然后我再想著刪除不需要的依賴項(xiàng),這里用到了一個(gè)小工具,可以查看哪些包未被使用。
# 安裝 npm install -g depcheck # 安裝完成后輸入該命令查看為使用的包 depcheck # 對(duì)于不需要的包,使用以下命令刪除 npm uninstall xxx

找到不需要的包之后,去package.json里面把對(duì)應(yīng)的包全部刪除。
然后使用npm install命令,就可以重新整理依賴了。

可以看到刪了很多包,我再重新build一個(gè)試試。構(gòu)建完了,還是有17.6mb,并沒(méi)有起到什么作用
刪除sourceMap
在瀏覽網(wǎng)頁(yè)的過(guò)程中,我看到一種解決措施,可以將打包好的build文件夾中的map文件去掉,這樣會(huì)讓整體變小很多
所以我又查詢了map文件是做什么的
打包后產(chǎn)生后綴名為.map的文件是由于配置了sourcemap選項(xiàng)生成的,打包后的文件不容易找到出bug對(duì)應(yīng)的源代碼的位置,sourcemap就是來(lái)幫我們解決這個(gè)問(wèn)題的,有了map就可以像未壓縮的代碼一樣,準(zhǔn)確的輸出是哪一行哪一列有錯(cuò)。
可以看到map是用來(lái)在報(bào)錯(cuò)時(shí)定位源碼位置的,是個(gè)不錯(cuò)的功能,但我打包好的build文件夾一共17mb,其中map文件就占了12mb
所以生產(chǎn)模式部署到服務(wù)器上,如果map文件太大還是刪除了比較好
以下是我查到的一種比較合適的刪除map文件的方法:
因?yàn)閣ebpack的代碼中在打包時(shí)會(huì)自動(dòng)讀取env文件
// 加載環(huán)境變量
require('../config/env')
所以我們可以利用這個(gè)性質(zhì),在根目錄下創(chuàng)建一個(gè).env的文件,并在里面輸入

# 該變量用于控制讓 npm run build 是否會(huì)生成map文件,為false則不生產(chǎn),true則生成 GENERATE_SOURCEMAP = false
這樣設(shè)置完之后,再次打包build,發(fā)現(xiàn)文件大小只剩下5mb了
Terser壓縮
然后我又搜索了相關(guān)資料,我使用的打包是webpack5,發(fā)現(xiàn)可以使用TerserWebpackPlugin對(duì)代碼進(jìn)行壓縮,它不僅可以壓縮,還可以順便進(jìn)行代碼混淆,改所有變量名,去掉所有縮進(jìn)空格然后壓縮到一行里
閱讀了webpack的官方文檔之后,我對(duì)插件配置進(jìn)行了一些調(diào)整(官方文檔:https://webpack.docschina.org/plugins/terser-webpack-plugin/)
在webpack.config.js中進(jìn)行相關(guān)配置
// 這是我TerserPlugin打包的相關(guān)配置,在optimization下進(jìn)行配置
new TerserPlugin({
parallel: true, // 并行打包,打包加速
extractComments: false, // 刪掉注釋,若為true,表示會(huì)將注釋抽取到一個(gè)單獨(dú)的文件中
terserOptions: {
parse: {
ecma: 8,
},
compress: {
ecma: 5,
warnings: false,
comparisons: false,
inline: 2,
},
toplevel: true,
mangle: true,
keep_classnames: false,
keep_fnames: false,
output: {
ecma: 5,
comments: false,
ascii_only: true,
},
},
}),
這樣設(shè)置之后再次打包,發(fā)現(xiàn)build文件夾只有4.35mb了,已經(jīng)比一開(kāi)始小很多了,但感覺(jué)加載速度還是不會(huì)非常流暢。
SplitChunksPlugin分包
Code Splitting拆包優(yōu)化的最終目標(biāo)是什么?
- 把更新頻率低的代碼和內(nèi)容頻繁變動(dòng)的代碼分離,把共用率較高的資源也拆出來(lái),最大限度利用瀏覽器緩存。
- 減少 http 請(qǐng)求次數(shù)的同時(shí)避免單個(gè)文件太大以免拖垮響應(yīng)速度,也就是拆包時(shí)盡量實(shí)現(xiàn)文件個(gè)數(shù)更少、單個(gè)文件體積更小。
這是這部分的官方文檔:https://webpack.docschina.org/plugins/split-chunks-plugin
以下是我的配置
optimization: {
splitChunks: {
chunks: 'initial',
minSize: 20000,
minRemainingSize: 0,
minChunks: 1,
maxAsyncRequests: 30,
maxInitialRequests: 30,
enforceSizeThreshold: 50000,
cacheGroups: {
defaultVendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10,
reuseExistingChunk: true,
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true,
},
},
},
這樣可以將重復(fù)使用的一些js文件比如組件庫(kù)的代碼,拆成多份,按需分塊加載
路由按需懶加載
在React16.6.0版本中,新增了React.lazy函數(shù),它能讓你像常規(guī)組件一樣處理動(dòng)態(tài)引入的組件,配合 webpack 的 Code Splitting,只有當(dāng)組件被加載,對(duì)應(yīng)的資源才會(huì)導(dǎo)入,從而達(dá)到懶加載的效果。
一般懶加載和動(dòng)態(tài)路由一起使用,本文以靜態(tài)路由舉例,動(dòng)態(tài)路由多了個(gè)動(dòng)態(tài)生成路由的過(guò)程,技術(shù)層面本質(zhì)對(duì)優(yōu)化有用的還是懶加載。
// 路由懶加載demo
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';
const Home = lazy(() => import('./routes/Home'));
const TestPage= lazy(() => import('./routes/TestPage'));
const App = () => (
<BrowserRouter>
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route exact path="/" component={Home}/>
<Route path="/testPage" component={TestPage}/>
</Routes>
</Suspense>
</Router>
</BrowserRouter>
)
我使用的React版本是18.2.0,只需要按上面的寫法,就可以直接實(shí)現(xiàn)代碼分塊+路由懶加載了,其余設(shè)置在creat-react-app的時(shí)候已經(jīng)自動(dòng)配置好相關(guān)文件了
這樣實(shí)現(xiàn)的懶加載其中代碼分包已經(jīng)集成進(jìn)去了,webpack會(huì)根據(jù)你路由懶加載這部分的分塊,自動(dòng)對(duì)代碼進(jìn)行拆包。
PS:其中有一點(diǎn)需要注意的是,<Suspense>一定是在<Router>的下級(jí),Route或Routes的上級(jí),不能放在Routes和Route之間,因?yàn)檫@個(gè)問(wèn)題我卡了大半天,一開(kāi)始看著demo隨便放的,沒(méi)在意,結(jié)果報(bào)錯(cuò)在網(wǎng)上查了半天也查不到,最后自己沒(méi)注意試出來(lái)的。

這樣弄完之后就沒(méi)有大的代碼塊了,只需讀取較小的文件,就能顯示出主頁(yè)來(lái),其余部分按需懶加載,用戶體驗(yàn)感upup
小技巧
還有個(gè)小技巧,如果是在路由的地方,頁(yè)面級(jí)別的懶加載,如果直接按上文那么寫的話,在切換頁(yè)面的時(shí)候會(huì)閃爍一下,因?yàn)橛袀€(gè)懶加載的過(guò)程,就算加載的再快也會(huì)閃爍一下<Suspense>組件未加載完等待的效果。
這本身是沒(méi)什么問(wèn)題的,切頁(yè)面閃一下,但如果你當(dāng)前頁(yè)面有不希望閃爍的地方,它會(huì)跟著一起閃爍,無(wú)論那部分你是否設(shè)置了懶加載
舉個(gè)例子,就比如你路由用到了Layout,如下圖,你的<Suspense>組件不能放在Route中間,只能整個(gè)包裹Routes,所以要閃爍它都會(huì)一起閃,這樣給用戶的體驗(yàn)就會(huì)很差

解決方法:
把每個(gè)需要懶加載的頁(yè)面使用函數(shù)式組件包裝一下,把<Suspense>換個(gè)位置放,這樣layout就不會(huì)跟著其他懶加載頁(yè)面一起閃了
# 函數(shù)部分
const lazyComponent = (element) => {
return (
<Suspense fallback={<p>loading...</p>}>
{element}
</Suspense>
)
}
# ----------------------------------------------------------------------------
# return的ReactNode部分
<Route element={<StuLayout />}>
<Route path='/StuIndex' index element={lazyComponent(<StuIndex />)} />
<Route path='/StuIndex/ViewAnn' element={lazyComponent(<ViewAnn />)} />
</Route>
到此這篇關(guān)于React首頁(yè)加載速度優(yōu)化小結(jié)的文章就介紹到這了,更多相關(guān)React首頁(yè)加載速度內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
React Antd Select組件輸入搜索時(shí)調(diào)用接口方式
為優(yōu)化Select組件中文輸入時(shí)的接口調(diào)用,使用lodash.debounce實(shí)現(xiàn)防抖,避免每輸入一個(gè)字母即觸發(fā)請(qǐng)求,待用戶點(diǎn)擊空格完成輸入后,再調(diào)用接口獲取數(shù)據(jù),提升性能與用戶體驗(yàn)2025-08-08
淺談React19事件調(diào)度的設(shè)計(jì)思路
本文主要介紹了React選擇MessageChannel作為事件調(diào)度機(jī)制的原因,essageChannel屬于宏任務(wù),延遲極低且不會(huì)阻塞渲染,能夠滿足React在不阻塞瀏覽器的前提下,盡可能多地推進(jìn)Fiber渲染進(jìn)度的需求2026-05-05
在React Router中使用JWT的實(shí)現(xiàn)
本文主要介紹了在React Router中使用JWT的實(shí)現(xiàn),包括創(chuàng)建AuthProvider和AuthContext,實(shí)現(xiàn)ProtectedRoute組件,以及在React項(xiàng)目中處理公共路由和受校驗(yàn)保護(hù)路由,下面就來(lái)詳細(xì)的介紹一下2026-06-06
React Grid Layout基礎(chǔ)使用示例教程
React Grid Layout是一個(gè)用于在React應(yīng)用程序中創(chuàng)建可拖拽和可調(diào)整大小的網(wǎng)格布局的庫(kù),通過(guò)使用React Grid Layout,我們可以輕松地創(chuàng)建自適應(yīng)的網(wǎng)格布局,并實(shí)現(xiàn)拖拽和調(diào)整大小的功能,本文介紹了React Grid Layout的基礎(chǔ)使用方法,感興趣的朋友一起看看吧2024-02-02
React中狀態(tài)設(shè)置this.setState()的實(shí)現(xiàn)
本文對(duì)比了React中類組件和函數(shù)組件的狀態(tài)管理方式,前者合并更新,后者替換更新,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2025-09-09
詳解使用React進(jìn)行組件庫(kù)開(kāi)發(fā)
本篇文章主要介紹了詳解使用React進(jìn)行組件庫(kù)開(kāi)發(fā),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-02-02
npx create-react-app xxx創(chuàng)建項(xiàng)目報(bào)錯(cuò)的解決辦法
這篇文章主要介紹了npx create-react-app xxx創(chuàng)建項(xiàng)目報(bào)錯(cuò)的解決辦法,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-02-02

