吐司问卷:性能优化
Date: March 9, 2025 10:39 PM (GMT+8)
目标
- 缓存数据,減少计算
- 代码分析和拆分,优化首页代码体积
**注意事项:**优化要根据实际情况,不要先行优化
React 优化
要点:
- 缓存数据,减少计算
useState 传入函数
要点:
- useState 传入普通变量,每次组件更新都会执行
- useState 传入函数,只在组件渲染时执行一次
- 适合数据结构复杂、计算成本高的场景,
Case: useState 传入普通变量,每次组件更新都会执行
import React, { FC, useState } from 'react'
const Demo: FC = () => {
console.log('Render');
const [arr, setArr] = useState(['A', 'A', 'A'])
const handleClick = () => {
setArr(arr => [...arr, 'd'])
}
return (
<div>
<div>{arr}</div>
<button onClick={handleClick}>add</button>
</div>
)
}
export default Demo
Case: useState 传入函数,只在组件渲染时执行一次
import React, { FC, useState } from 'react'
const genArr = () => {
console.log('genArr...');
const arr = Array(3).fill('A')
return arr
}
const Demo: FC = () => {
console.log('Render');
const [arr, setArr] = useState(genArr)
const handleClick = () => {
setArr(arr => [...arr, 'd'])
}
return (
<div>
<div>{arr}</div>
<button onClick={handleClick}>add</button>
</div>
)
}
export default Demo
理解:genArr
只在组件渲染时执行一次
useMemo
要点:
- 函数组件,每次 state 更新都会重新执行函数
- useMemo 可以缓存数据,不用每次执行函数都重新生成
- 可用于计算量较大的场景,缓存提高性能
useMemo使用场景:
- 依赖项不经常变化
- 缓存的元素创建成本较高
Case: 二维码生成
比如生成二维码,依赖项为 id 和 isPublished,二维码的创建成本一般较高,就可以采用 useMemo
import React, { FC, useRef, useMemo } from 'react'
import {
Space,
Button,
Typography,
Input,
InputRef,
message,
Popover,
} from 'antd'
import styles from './StatHeader.module.scss'
import { CopyOutlined, LeftOutlined, QrcodeOutlined } from '@ant-design/icons'
import useGetPageInfo from '../../../hooks/useGetPageInfo'
import { useNavigate, useParams } from 'react-router-dom'
import { QRCodeSVG } from 'qrcode.react'
const { Title } = Typography
const StatHeader: FC = () => {
const { id } = useParams()
const { title } = useGetPageInfo()
const nav = useNavigate()
const { isPublished } = useGetPageInfo()
const urlInputRef = useRef<InputRef>(null)
function copy() {
const input = urlInputRef.current
if (!input) return
input.select()
document.execCommand('copy')
message.success('复制成功')
}
const LinkAndQRCodeElem = useMemo(() => {
if (!isPublished) return null
const url = `http://localhost:3000/questionnaire/${id}`
const QRCodeElem = (
<div style={{ textAlign: 'center' }}>
<QRCodeSVG value={url} size={150} />
</div>
)
return (
<Space>
<Input value={url} style={{ width: '300px' }} ref={urlInputRef} />
<Button icon={<CopyOutlined />} onClick={copy}></Button>
<Popover content={QRCodeElem}>
<Button icon={<QrcodeOutlined />}></Button>
</Popover>
</Space>
)
}, [id, isPublished])
return (
<div className={styles['header-wrapper']}>
<div className={styles.header}>
<div className={styles.left}>
<Space align="center">
<Button type="link" icon={<LeftOutlined />} onClick={() => nav(-1)}>
返回
</Button>
<Title>{title}</Title>
</Space>
</div>
<div className={styles.main}>{LinkAndQRCodeElem}</div>
<div className={styles.right}>
<Button
type="primary"
onClick={() => {
nav(`/question/edit/${id}`)
}}
>
编辑问卷
</Button>
</div>
</div>
</div>
)
}
export default StatHeader
useCallback
要点:
- 和 useMemo 作用一样
- 专门用于缓存函数
Case:
import React, { useState, memo, useMemo, useCallback } from 'react'
// 1-memo包裹子组件
const Child = memo(({ userInfo, onChange }) => {
console.log('Child render')
return (
<div>
<p>Name is {userInfo.name}</p>
<input type="text" onChange={onChange} />
</div>
)
})
function App() {
const [count, setCount] = useState(0)
const [name, setName] = useState('Nathan')
// 2-useMemo缓存数据
const userInfo = useMemo(() => {
return { name, age: 20 }
}, [name])
const handleChange = useCallback(e => {
console.log('onChange', e.target.value)
}, [])
console.log('Parent render')
return (
<div>
<p>Count is {count}</p>
<button onClick={() => setCount(count + 1)}>+1</button>
<Child userInfo={userInfo} onChange={handleChange} />
</div>
)
}
export default App
参考:React Hooks
React.memo
- state 变化,组件会更新
- 同时,子组件默认会“无脑”更新,无论 props 是否变化
- 根要控制子组件更新,需使用 React.memo
优化代码体积
代码分析
要点:
- 通过分析工具,分析各包的大小,并通过 Serve 查看对应包体积
笔记:
Source map explorer,分析 JavaScript 包。这有助于了解代码膨胀的来源。
npm install --save source-map-explorer
然后在 package.json
中,添加以下行到 scripts
:
"scripts": {
+ "analyze": "source-map-explorer 'build/static/js/*.js'",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
构建包 & 分析包:
npm run build
npm run analyze
参考文档:
https://create-react-app.dev/docs/analyzing-the-bundle-size
**打包资源图:**根据图分析如何进行拆包处理
分包前 main 包大小为 1.3MB
serve -s build
路由懒加载
目标:
- 首页加载速度提升
分析:
资源图分析:@dnd-kit,recharts 资源体积占比很高,它仅用于统计分析页、编辑器。
而首页用不到这些功能,因此可以将它们拆分出去。
解决方案:
我们可以通过路由懒加载,拆分 bundle,以此优化首页体积。
index.tsx
// import Edit from '../pages/question/Edit'
// import Stat from '../pages/question/Stat'
const Edit = lazy(
() => import(/* webpackChunkName: "editPage" */ '../pages/question/Edit')
)
const Stat = lazy(
() => import(/* webpackChunkName: "statPage" */ '../pages/question/Stat')
)
进行打包与分析:
可以看到 main 包从 1.7 MB 降低到了 1.2 MB
npm run build
分包加载
目标:
- 拆分 antd 和 react-dom
分析:
在资源图中,antd 和 react-dom 占比很高,我们考虑将其拆分出去。
思考:
- andt、react-dom 包版本不常更新,可以拆分,并通过浏览器进行缓存
比如浏览器第一次请求服务器资源,会返回 src、antd、react-dom 等包。
浏览器将 antd、react-dom 缓存。之后,浏览器再次请求,服务端就不用发送这些包了。
- 在生产环境应用以上规则
分包方式:
将包分成 src、antd、react-dom 以及第三包 这四种包
craco.config
webpack: {
configure: webpackConfig => {
// 生产环境:抽离公共代码
if (webpackConfig.mode === 'production') {
if (webpackConfig.optimization === null) {
webpackConfig.optimization = {}
}
webpackConfig.optimization.splitChunks = {
chunks: 'all',
// 采用缓存,提高打包速度
cacheGroups: {
antd: {
name: 'antdchunk',
test: /antd/,
priority: 100,
},
reactDom: {
name: 'reactDom-chunk',
test: /react-dom/,
priority: 99,
},
// 第三方插件
vendors: {
name: 'vendors-chunk',
test: /node_modules/,
priority: 98,
},
},
}
}
return webpackConfig
},
},
npm run build
后可见分析(以下是压缩后):
File sizes after gzip:
343.21 kB (+18.63 kB) build/static/js/vendors-chunk.7bb93744.js
138.79 kB (+400 B) build/static/js/antdchunk.44c16e36.js
41.45 kB build/static/js/reactDom-chunk.1439cf47.js
9.59 kB (+554 B) build/static/js/main.29ab4e1f.js
3.88 kB (+10 B) build/static/js/editPage.b9332f3b.chunk.js
3.36 kB (+38 B) build/static/js/766.9f144573.chunk.js
2.44 kB (-7 B) build/static/js/statPage.85a628ec.chunk.js
955 B build/static/css/editPage.2fabb9d5.chunk.css
820 B build/static/css/main.d0183c63.css
633 B (+34 B) build/static/css/statPage.82d41fdf.chunk.css
分包后可见 main包为 26.3 KB :
资源图如下所示:
参考:
- https://www.webpackjs.com/plugins/split-chunks-plugin/#root
CSS拆分
Craco 会自动根据路由来拆分css,所以以上路由懒加载实现后,Craco 已经拆分出 editPage、statPage 的 css 文件,如下所示:
File sizes after gzip:
343.21 kB (+18.63 kB) build/static/js/vendors-chunk.7bb93744.js
138.79 kB (+400 B) build/static/js/antdchunk.44c16e36.js
41.45 kB build/static/js/reactDom-chunk.1439cf47.js
9.59 kB (+554 B) build/static/js/main.29ab4e1f.js
3.88 kB (+10 B) build/static/js/editPage.b9332f3b.chunk.js
3.36 kB (+38 B) build/static/js/766.9f144573.chunk.js
2.44 kB (-7 B) build/static/js/statPage.85a628ec.chunk.js
955 B build/static/css/editPage.2fabb9d5.chunk.css
820 B build/static/css/main.d0183c63.css
633 B (+34 B) build/static/css/statPage.82d41fdf.chunk.css