吐司问卷:性能优化

吐司问卷:性能优化

Date: March 9, 2025 10:39 PM (GMT+8)


目标

  • 缓存数据,減少计算
  • 代码分析和拆分,优化首页代码体积

**注意事项:**优化要根据实际情况,不要先行优化




React 优化

要点:

  • 缓存数据,减少计算

useState 传入函数

要点:

  • useState 传入普通变量,每次组件更新都会执行
  • useState 传入函数,只在组件渲染时执行一次
  • 适合数据结构复杂、计算成本高的场景,

Case: useState 传入普通变量,每次组件更新都会执行

2025-03-09 23.29.35.gif

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 传入函数,只在组件渲染时执行一次

2025-03-09 23.38.10.gif

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: 二维码生成

image.png

比如生成二维码,依赖项为 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:

2025-02-14 23.29.16.gif

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


**打包资源图:**根据图分析如何进行拆包处理

image.png

分包前 main 包大小为 1.3MB

serve -s build

image.png



路由懒加载

目标:

  • 首页加载速度提升

分析:

资源图分析:@dnd-kit,recharts 资源体积占比很高,它仅用于统计分析页、编辑器。

而首页用不到这些功能,因此可以将它们拆分出去。

image.png

解决方案:

我们可以通过路由懒加载,拆分 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

image.png

image.png



分包加载

目标:

  • 拆分 antd 和 react-dom

image.png


分析:

在资源图中,antd 和 react-dom 占比很高,我们考虑将其拆分出去。

思考:

  • andt、react-dom 包版本不常更新,可以拆分,并通过浏览器进行缓存

比如浏览器第一次请求服务器资源,会返回 src、antd、react-dom 等包。

浏览器将 antd、react-dom 缓存。之后,浏览器再次请求,服务端就不用发送这些包了。

image.png

  • 在生产环境应用以上规则

分包方式:

将包分成 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 :

image.png

资源图如下所示:

image.png

参考:

  • 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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值