React首页加载速度优化

问题描述

在将本地react项目部署至服务器,域名nginx反向代理证书什么的全都弄好,运行成功能够访问之后,觉得一切万事大吉。

但发现在首次打开项目页面时,加载的特别慢,用了七八秒才加载完成,体验比较差,于是决定对加载速度进行一些优化。

问题分析

用网页控制台查看了一下网页加载的进度,定位到了加载速度很慢的地方

发现下载网页的js文件,有15mb大小,用了8秒,然后看了下打包好的build文件夹,有19.1mb。

这文件大小有点离谱了啊,于是决定对整个项目部署进行精简。

问题探索

代码精简自查

我首先想到的是删除每个js文件中没有使用到的导入,简化一下文件依赖结构,再删除一些不需要的代码,清理一下用不到的图片素材之类的。

删除这个的时候,我没找到什么好办法,我使用的是vscode自带的功能,自动整理所有未使用的import。

使用方法:

打开左上角,文件-首选项-设置,输入"setting",选择"字体",点击"在settings.json中编辑",在json文件中输入

json 复制代码
"editor.codeActionsOnSave": { "source.organizeImports": true },

输入这个之后,每个js文件,在保存时会自动去除没有使用的import。

全部删除之后,build完,小了一点,但依然还有17.7mb,这很显然还是不满足要求。


删除没用的包

然后我再想着删除不需要的依赖项,这里用到了一个小工具,可以查看哪些包未被使用。

bash 复制代码
# 安装
npm install -g depcheck
# 安装完成后输入该命令查看为使用的包
depcheck
# 对于不需要的包,使用以下命令删除
npm uninstall xxx

找到不需要的包之后,去package.json里面把对应的包全部删除。

然后使用npm install命令,就可以重新整理依赖了。

可以看到删了很多包,我再重新build一个试试。构建完了,还是有17.6mb,并没有起到什么作用


删除sourceMap

在浏览网页的过程中,我看到一种解决措施,可以将打包好的build文件夹中的map文件去掉,这样会让整体变小很多

所以我又查询了map文件是做什么的

打包后产生后缀名为.map的文件是由于配置了sourcemap选项生成的,打包后的文件不容易找到出bug对应的源代码的位置,sourcemap就是来帮我们解决这个问题的,有了map就可以像未压缩的代码一样,准确的输出是哪一行哪一列有错。

可以看到map是用来在报错时定位源码位置的,是个不错的功能,但我打包好的build文件夹一共17mb,其中map文件就占了12mb

所以生产模式部署到服务器上,如果map文件太大还是删除了比较好

以下是我查到的一种比较合适的删除map文件的方法:

因为webpack的代码中在打包时会自动读取env文件

react 复制代码
// 加载环境变量
require('../config/env')

所以我们可以利用这个性质,在根目录下创建一个.env的文件,并在里面输入

复制代码
# 该变量用于控制让 npm run build 是否会生成map文件,为false则不生产,true则生成
GENERATE_SOURCEMAP = false

这样设置完之后,再次打包build,发现文件大小只剩下5mb了


Terser压缩

然后我又搜索了相关资料,我使用的打包是webpack5,发现可以使用TerserWebpackPlugin对代码进行压缩,它不仅可以压缩,还可以顺便进行代码混淆,改所有变量名,去掉所有缩进空格然后压缩到一行里

阅读了webpack的官方文档之后,我对插件配置进行了一些调整(官方文档:https://webpack.docschina.org/plugins/terser-webpack-plugin/)

在webpack.config.js中进行相关配置

json 复制代码
// 这是我TerserPlugin打包的相关配置,在optimization下进行配置
new TerserPlugin({
          parallel: true,	// 并行打包,打包加速
          extractComments: false,	// 删掉注释,若为true,表示会将注释抽取到一个单独的文件中
          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,
            },
          },
        }),

这样设置之后再次打包,发现build文件夹只有4.35mb了,已经比一开始小很多了,但感觉加载速度还是不会非常流畅。


SplitChunksPlugin分包*--不需要*

Code Splitting拆包优化的最终目标是什么?

  1. 把更新频率低的代码和内容频繁变动的代码分离,把共用率较高的资源也拆出来,最大限度利用浏览器缓存。
  2. 减少 http 请求次数的同时避免单个文件太大以免拖垮响应速度,也就是拆包时尽量实现文件个数更少、单个文件体积更小。

这是这部分的官方文档:https://webpack.docschina.org/plugins/split-chunks-plugin

以下是我的配置

json 复制代码
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,
        },
      },
    },

这样可以将主要的打包js文件拆成多份,按需分块加载


路由按需懒加载

在React16.6.0版本中,新增了React.lazy函数,它能让你像常规组件一样处理动态引入的组件,配合 webpack 的 Code Splitting,只有当组件被加载,对应的资源才会导入,从而达到懒加载的效果。

react 复制代码
// 路由懒加载demo
import { BrowserRouter as Router, Route, Switch } from 'react-router-dom';
import React, { Suspense, lazy } from 'react';

const Home = lazy(() => import('./routes/Home'));
const UserManage = lazy(() => import('./routes/UserManage'));
const AssetManage = lazy(() => import('./routes/AssetManage'));
const AttendanceManage = lazy(() => import('./routes/AttendanceManage'));

const App = () => (	
      <BrowserRouter>
         <Router>
    		<Suspense fallback={<div>Loading...</div>}>
      			<Routes>
        			<Route exact path="/" component={Home}/>
        			<Route path="/userManage" component={UserManage}/>
        			<Route path="/assetManage" component={AssetManage}/>
        			<Route path="/attendanceManage" component={AttendanceManage}/>
      			</Routes>
    		</Suspense>
  		</Router> 
      </BrowserRouter>
)

我使用的React版本是18.2.0,只需要按上面的写法,就可以直接实现代码分块+路由懒加载了,其余设置在creat-react-app的时候已经自动配置好相关文件了

这样实现的懒加载其中代码分包已经集成进去了,webpack会根据你路由懒加载这部分的分块,自动对代码进行拆包,不需要手动再去改webpack代码拆包相关的,枉费我研究了一整天SplitChunksPlugin原理,差点就要纯手动拆包了,还好试着研究了一下这部分

PS:其中有一点需要注意的是,<Suspense>一定是在<Router>的下级,Route或Routes的上级,不能放在Routes和Route之间,因为这个问题我卡了大半天,一开始看着demo随便放的,没在意,结果报错在网上查了半天也查不到,最后自己没注意试出来的。

这样弄完之后就没有大的代码块了,只需读取较小的文件,就能显示出主页来,其余部分按需懒加载,用户体验感upup

小技巧

还有个小技巧,如果是在路由的地方,页面级别的懒加载,如果直接按上文那么写的话,在切换页面的时候会闪烁一下,因为有个懒加载的过程,就算加载的再快也会闪烁一下<Suspense>组件未加载完等待的效果。

这本身是没什么问题的,切页面闪一下,但如果你当前页面有不希望闪烁的地方,它会跟着一起闪烁,无论那部分你是否设置了懒加载

举个例子,就比如你路由用到了Layout,如下图,你的<Suspense>组件不能放在Route中间,只能整个包裹Routes,所以要闪烁它都会一起闪,这样给用户的体验就会很差

解决方法:

把每个需要懒加载的页面使用函数式组件包装一下,把<Suspense>换个位置放,这样layout就不会跟着其他懒加载页面一起闪了

react 复制代码
# 函数部分
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>
相关推荐
JiangJiang7 分钟前
🚀 Vue人看React useRef:它不只是替代 ref
javascript·react.js·面试
1024小神11 分钟前
在GitHub action中使用添加项目中配置文件的值为环境变量
前端·javascript
龙骑utr16 分钟前
qiankun微应用动态设置静态资源访问路径
javascript
Jasmin Tin Wei16 分钟前
css易混淆的知识点
开发语言·javascript·ecmascript
齐尹秦19 分钟前
CSS 列表样式学习笔记
前端
wsz777724 分钟前
js封装系列(一)
javascript
Mnxj24 分钟前
渐变边框设计
前端
糯糯机器24 分钟前
Webpack 打包未使用组件的原因
webpack
用户76787977373226 分钟前
由Umi升级到Next方案
前端·next.js
快乐的小前端27 分钟前
TypeScript基础一
前端