React 性能优化指南
useState
: 传入函数, 避免重复计算。useMemo
: 缓存计算结果,缓存数据。useCallback
: 缓存函数。React.memo
: 缓存子组件。- 优化代码体积,使用
source-map-explorer
分析代码体积,使用webpack-bundle-analyzer
分析代码结构。
useState
- 当
useState
参数传入函数时,渲染只会调用一次(初始化时候),后续更新不会再调用。 - 适合场景:当初始化的值需要复杂计算时,可以使用函数传入,避免重复计算。
import React, { useState } from 'react'
export default function App() {
const [count, setCount] = useState(() => {
// 仅在初始化时调用,结果会被缓存
console.log('render')
return 0
})
return (
<div>
<p>{count}</p>
<button onClick={() => setCount(count + 1)}>+</button>
</div>
)
}
useMemo
useMemo
: 可以缓存数据,不用每次执行函数都重新生成。- 适合场景: 可用用户计算量比较大的场景,缓存提高性能。
- 使用
useMemo
,要注意依赖项,当依赖项发生变化时,会重新计算, 如果依赖项经常变化,缓存的成本要较高,所以要去权衡。
import React, { useState, useMemo } from 'react'
export default function App() {
const [count, setCount] = useState(0)
const [value, setValue] = useState('')
// 缓存数据
const expensive = useMemo(() => {
console.log('compute')
let sum = 0
for (let i = 0; i < count * 100; i++) {
sum += i
}
return sum
}, [count])
return (
<div>
<h4>
{count}-{expensive}
</h4>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={value} onChange={(event) => setValue(event.target.value)} />
</div>
)
}
useCallback
useCallback
: 缓存函数,不用每次渲染都重新生成函数。- 适用场景:当子组件接收函数作为
props
时,可以使用useCallback
缓存函数,避免子组件重复渲染, 也可以在组件中,经常调用函数进行缓存 。
import React, { useState, useCallback } from 'react'
export default function App() {
const [count, setCount] = useState(0)
const [value, setValue] = useState('')
// 缓存函数
const callback = useCallback(() => {
console.log('callback')
return count
}, [count])
return (
<div>
<h4>
{count}-{callback()}
</h4>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={value} onChange={(event) => setValue(event.target.value)} />
</div>
)
}
React.memo
React.memo
: 缓存子组件,避免重复渲染。- 适用场景:当子组件只依赖
props
时,可以使用React.memo
缓存子组件,避免重复渲染。
import React, { useState, memo } from 'react'
// 缓存子组件
const Child = memo(function Child({ data }) {
console.log('render child')
return <div>{data}</div>
})
// 父组件
export default function App() {
const [count, setCount] = useState(0)
const [value, setValue] = useState('')
return (
<div>
<h4>{count}</h4>
<button onClick={() => setCount(count + 1)}>+</button>
<input value={value} onChange={(event) => setValue(event.target.value)} />
<Child data={value} />
</div>
)
}
优化代码体积
使用
source-map-explorer
分析代码体积,使用webpack-bundle-analyzer
分析代码结构。安装
source-map-explorer
。
npm install source-map-explorer --save-dev
- 在
package.json
中添加命令。
{
"scripts": {
"analyze": "source-map-explorer 'build/static/js/*.js'"
}
}
- 运行命令。
npm run build
npm run analyze
在路由使用懒加载
import React, { lazy, Suspense } from 'react' import { BrowserRouter as Router, Route, Switch } from 'react-router-dom' const Home = lazy(() => import( /* webpackChunkName: "home" */ './pages/Home' ) ) const About = lazy(() => import( /* webpackChunkName: "about" */ './pages/About' ) ) export default function App() { return ( <Router> <Suspense fallback={<div>Loading...</div>}> <Switch> <Route path='/' exact component={Home} /> <Route path='/about' component={About} /> </Switch> </Suspense> </Router> ) }
抽离公共代码
在
craco.config.js
中配置splitChunks
。terser-webpack-plugin
开启压缩。compression-webpack-plugin
开启gzip
压缩。tree shaking
:webpack
默认支持tree shaking
,但是需要注意的是,tree shaking
只支持ES Module
的引入方式,不支持CommonJS
的引入方式。ES Module
的引入方式:import { Button } from 'antd'
- 配置
optimization.usedExports
为true
开启tree shaking
。
const path = require('path') const { name } = require('./package.json') const TerserPlugin = require('terser-webpack-plugin') const pathResolve = (pathUrl) => path.join(__dirname, pathUrl) module.exports = { webpack: { configure(webpackConfig) { // 配置扩展扩展名优化 webpackConfig.resolve.extensions = [ '.tsx', '.ts', '.jsx', '.js', '.scss', '.css', '.json', ] // 抽离公共代码 if (process.env.NODE_ENV === 'production') { // splitChunks打包优化 webpackConfig.optimization.splitChunks = { chunks: 'all', // 策略配置 cacheGroups: { antd: { name: 'chunk-antd', // 单独将 antd 拆包 test: /antd/, priority: 100, }, reactDom: { name: 'chunk-react-dom', // 单独将 react-dom 拆包 test: /react-dom/, priority: 99, }, vendors: { test: /[\\/]node_modules[\\/]/, name: 'chunk-vendors', priority: 98, }, }, } // 插件配置 webpackConfig.plugins = webpackConfig.plugins.concat([ // 开启gzip压缩 new CompressionWebpackPlugin({ test: /\.(js|ts|jsx|tsx|css|scss)$/, //匹配要压缩的文件 algorithm: 'gzip', // gzip or brotli plugin }), ]) // tree shaking 开启 // webpackConfig.optimization.usedExports = true // js 压缩 webpackConfig.optimization.minimizer = [ new TerserPlugin({ terserOptions: { parallel: true, // 开启多进程压缩 compress: { drop_console: true, drop_debugger: true, }, }, }), ] } else { // 默认配置 webpackConfig.optimization.splitChunks = {} } // 开启持久化缓存 webpackConfig.cache.type = 'filesystem' return webpackConfig }, }, }
参考资料