目录
[常见的 React 性能优化手段](#常见的 React 性能优化手段)
[1. 使用 useMemo 和 useCallback 缓存数据和函数](#1. 使用 useMemo 和 useCallback 缓存数据和函数)
[2. 使用 React.memo 缓存组件](#2. 使用 React.memo 缓存组件)
[3. 组件懒加载](#3. 组件懒加载)
[4. 合理使用 key](#4. 合理使用 key)
[5. 在组件销毁时清除定时器/事件](#5. 在组件销毁时清除定时器/事件)
[6. 使用 Suspense 和 Lazy 拆分组件](#6. 使用 Suspense 和 Lazy 拆分组件)
[7. 使用 Fragment 避免额外标记](#7. 使用 Fragment 避免额外标记)
[8. 避免使用内联函数](#8. 避免使用内联函数)
[9. 避免使用内联样式](#9. 避免使用内联样式)
[10. 优化渲染条件](#10. 优化渲染条件)
[11. 为组件创建错误边界](#11. 为组件创建错误边界)
[12. 组件卸载前进行清理操作](#12. 组件卸载前进行清理操作)
[13. 使用 PureComponent](#13. 使用 PureComponent)
[14. 使用 shouldComponentUpdate](#14. 使用 shouldComponentUpdate)
[15. 在构造函数中进行函数 this 绑定](#15. 在构造函数中进行函数 this 绑定)
[16. 类组件中的箭头函数](#16. 类组件中的箭头函数)
[17. 避免数据结构突变](#17. 避免数据结构突变)
[18. 依赖优化](#18. 依赖优化)
常见的 React 性能优化手段
在开发 React 应用时,性能优化是确保应用高效运行的关键。以下是常见的 React 性能优化手段,并附带代码示例和解释。
1. 使用 useMemo
和 useCallback
缓存数据和函数
useMemo
和 useCallback
是 React 提供的 Hooks,用于缓存计算结果和函数引用,避免不必要的重新渲染。
示例:
javascript
import React, { useState, useMemo, useCallback } from 'react';
const data = {
userName: '张三',
age: 19,
fav: '篮球、排球',
};
const getUserInfo = () => {
return {
...data,
random: Math.random(),
};
};
function Case2() {
const [count, setCount] = useState(0);
// 使用 useMemo 缓存数据
const userInfo = useMemo(() => getUserInfo(), []);
// 使用 useCallback 缓存函数
const handleClick = useCallback(() => {
setCount(count + 1);
}, [count]);
return (
<div>
<div>姓名:{userInfo.userName}</div>
<div>年龄:{userInfo.age}</div>
<div>爱好:{userInfo.fav}</div>
<div>随机数: {userInfo.random}</div>
<div>当前页面渲染次数: {count}</div>
<button onClick={handleClick}>刷新渲染组件</button>
</div>
);
}
解释:
useMemo
确保getUserInfo
的结果只在首次渲染时计算一次。
useCallback
确保handleClick
函数的引用不会在每次渲染时都重新创建。
2. 使用 React.memo
缓存组件
React.memo
类似于 shouldComponentUpdate
,当 props 没有变化时,不会重新渲染组件,从而提高性能。
示例:
javascript
import React from 'react';
import { Button } from 'antd';
const BasicButton = (props) => {
return <Button {...props}></Button>;
};
export default React.memo(BasicButton, (oldProps, newProps) => {
return oldProps === newProps; // true - 不更新 false - 更新
});
解释:
React.memo
接收一个比较函数,只有当 props 发生变化时才会重新渲染组件。
3. 组件懒加载
使用组件懒加载可以减少初始 bundle 文件大小,加快组件加载速度。
示例:
javascript
import React, { lazy, Suspense } from 'react';
import { BrowserRouter, Link, Route, Switch } from 'react-router-dom';
const Home = lazy(() => import(/* webpackChunkName: "Home" */ './Home'));
const List = lazy(() => import(/* webpackChunkName: "List" */ './List'));
function App() {
return (
<BrowserRouter>
<Link to="/">Home</Link>
<Link to="/list">List</Link>
<Switch>
<Suspense fallback={<div>Loading</div>}>
<Route path="/" component={Home} exact />
<Route path="/list" component={List} />
</Suspense>
</Switch>
</BrowserRouter>
);
}
export default App;
解释:
lazy
动态导入组件,Suspense
提供加载状态。
4. 合理使用 key
在 map
循环中尽量使用唯一的标识作为 key
,避免使用 index
作为 key
,方便复用组件。
示例:
javascript
import React from 'react';
const list = [
{ id: 1, name: '张三' },
{ id: 2, name: '李四' },
{ id: 3, name: '王五' },
];
function Case3() {
return (
<div>
{list.map((item) => (
<div key={item.id}>{item.name}</div>
))}
</div>
);
}
export default Case3;
解释: 使用
id
作为key
,确保每个元素的唯一性。
5. 在组件销毁时清除定时器/事件
组件卸载时清除相关事件、定时器,防止内存泄漏。
示例:
javascript
import React, { useEffect, useState } from 'react';
function Case1() {
const [count, setCount] = useState(0);
useEffect(() => {
const timer = setInterval(() => {
setCount(count + 1);
}, 1000);
return () => {
clearInterval(timer);
};
}, [count]);
return <div>{count}</div>;
}
export default Case1;
解释:
useEffect
返回的清理函数会在组件卸载时执行,清除定时器。
6. 使用 Suspense
和 Lazy
拆分组件
通过 Suspense
和 Lazy
实现按需加载组件,提升首屏加载速度。
示例:
javascript
import React, { lazy } from 'react';
import ReactDOM from 'react-dom/client';
import { createBrowserRouter, RouterProvider } from 'react-router-dom';
const LearnReactOptimize = lazy(() => import('./pages/LearnReactOptimize'));
const LazyBoundary = (WrapComp) => (
<Suspense fallback={<div>loading....</div>}>
<WrapComp />
</Suspense>
);
const routeConfig = createBrowserRouter([
{
path: '/LearnReactOptimize',
element: LazyBoundary(LearnReactOptimize),
},
{
path: '/',
element: <App />,
},
]);
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<RouterProvider router={routeConfig} />
</React.StrictMode>,
);
解释:
Suspense
提供加载状态,Lazy
动态导入组件。
7. 使用 Fragment
避免额外标记
通过 Fragment
减少不必要的标签,简化 DOM 结构。
示例:
javascript
import React from 'react';
// bad
function AppBad() {
return (
<div>
<div>1</div>
<div>2</div>
</div>
);
}
// good
function AppGood() {
return (
<>
<div>1</div>
<div>2</div>
</>
);
}
解释:
Fragment
(<>...</>
) 不会生成额外的 DOM 元素。
8. 避免使用内联函数
避免在 JSX 中使用内联函数,以减少不必要的函数创建。
示例:
javascript
import React from 'react';
// bad
function AppBad() {
const handleClick = () => {
console.log('click');
};
return <div onClick={() => handleClick()}>App</div>;
}
// good
function AppGood() {
const handleClick = () => {
console.log('click');
};
return <div onClick={handleClick}>App</div>;
}
**解释:**内联函数每次渲染都会创建新的函数实例,导致不必要的性能开销。
9. 避免使用内联样式
避免使用内联样式,将样式提取到 CSS 文件中,减少 JavaScript 执行时间。
示例:
javascript
import React from 'react';
import './App.css'; // 引入外部 CSS 文件
function App() {
return <div className="app-style">App works</div>;
}
export default App;
**解释:**将样式提取到外部 CSS 文件中,减少 JavaScript 执行时间。
10. 优化渲染条件
避免不必要的渲染,确保组件只在必要时更新。
示例:
javascript
import React, { useState, useEffect } from 'react';
function App() {
const [count, setCount] = useState(0);
const [name] = useState("张三");
useEffect(() => {
setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
}, []);
return (
<div>
{count}
<ShowNameMemo name={name} />
</div>
);
}
function ShowName({ name }) {
console.log("showName render...");
return <div>{name}</div>;
}
const ShowNameMemo = React.memo(ShowName);
export default App;
解释: 使用
React.memo
防止不必要的重新渲染。
11. 为组件创建错误边界
捕获子组件中的错误,防止整个组件树崩溃。
示例:
javascript
import React from 'react';
class ErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false };
}
static getDerivedStateFromError(error) {
return { hasError: true };
}
componentDidCatch(error, errorInfo) {
console.log(error, errorInfo);
}
render() {
if (this.state.hasError) {
return <h1>Something went wrong.</h1>;
}
return this.props.children;
}
}
export default ErrorBoundary;
解释:
ErrorBoundary
捕获子组件中的错误,显示降级 UI。
12. 组件卸载前进行清理操作
确保组件卸载时清理定时器、事件监听等资源。
示例:
javascript
import React, { useState, useEffect } from 'react';
import ReactDOM from 'react-dom';
const App = () => {
const [count, setCount] = useState(0);
useEffect(() => {
let timer = setInterval(() => {
setCount(prev => prev + 1);
}, 1000);
return () => {
clearInterval(timer);
};
}, []);
return (
<button onClick={() => ReactDOM.unmountComponentAtNode(document.getElementById('root'))}>
{count}
</button>
);
};
export default App;
解释:
useEffect
返回的清理函数会在组件卸载时执行,清除定时器。
13. 使用 PureComponent
PureComponent
是类组件的优化版本,自动实现浅比较,减少不必要的渲染。
示例:
javascript
import React from 'react';
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 1,
};
}
componentDidMount() {
this.setState({ count: 1 });
}
render() {
return (
<div>
<RegularChildComponent count={this.state.count} />
<PureChildComponent count={this.state.count} />
</div>
);
}
}
class RegularChildComponent extends React.Component {
render() {
console.log('RegularChildComponent render');
return <div>{this.props.count}</div>;
}
}
class PureChildComponent extends React.PureComponent {
render() {
console.log('PureChildComponent render');
return <div>{this.props.count}</div>;
}
}
export default App;
解释:
PureComponent
自动实现浅比较,减少不必要的渲染。
14. 使用 shouldComponentUpdate
手动控制组件是否需要更新。
示例:
javascript
import React from 'react';
export default class App extends React.Component {
constructor() {
super();
this.state = { name: '张三', age: 20, job: 'waiter' };
}
componentDidMount() {
setTimeout(() => this.setState({ job: 'chef' }), 1000);
}
shouldComponentUpdate(nextProps, nextState) {
if (this.state.name !== nextState.name || this.state.age !== nextState.age) {
return true;
}
return false;
}
render() {
console.log('rendering');
let { name, age } = this.state;
return <div>{name} {age}</div>;
}
}
解释:
shouldComponentUpdate
控制组件是否需要更新。
15. 在构造函数中进行函数 this
绑定
确保类方法中的 this
指向正确。
示例:
javascript
import React from 'react';
export default class App extends React.Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this);
}
handleClick() {
console.log(this);
}
render() {
return <button onClick={this.handleClick}>按钮</button>;
}
}
解释: 构造函数中绑定
this
,确保方法中的this
指向正确。
16. 类组件中的箭头函数
使用箭头函数避免 this
绑定问题。
示例:
javascript
import React from 'react';
export default class App extends React.Component {
handleClick = () => console.log(this);
render() {
return <button onClick={this.handleClick}>按钮</button>;
}
}
解释: 箭头函数自动绑定
this
,避免手动绑定。
17. 避免数据结构突变
保持组件中 props
和 state
的数据结构一致,避免突变。
示例:
javascript
import React, { Component } from 'react';
export default class App extends Component {
constructor() {
super();
this.state = {
employee: {
name: '张三',
age: 20,
},
};
}
render() {
const { name, age } = this.state.employee;
return (
<div>
{name}
{age}
<button
onClick={() =>
this.setState({
...this.state,
employee: {
...this.state.employee,
age: 30,
},
})
}
>
change age
</button>
</div>
);
}
}
**解释:**使用扩展运算符避免直接修改对象。
18. 依赖优化
优化第三方库的引入,减少打包体积。
示例:
-
安装依赖:
bashyarn add react-app-rewired customize-cra lodash babel-plugin-lodash
-
创建
config-overrides.js
:javascriptconst { override, useBabelRc } = require('customize-cra'); module.exports = override(useBabelRc());
-
修改
package.json
:javascript"scripts": { "start": "react-app-rewired start", "build": "react-app-rewired build", "test": "react-app-rewired test --env=jsdom", "eject": "react-scripts eject" }
-
创建
.babelrc
:javascript{ "plugins": ["lodash"] }
-
使用 Lodash:
javascriptimport React from 'react'; import _ from 'lodash'; function App() { console.log(_.chunk(['a', 'b', 'c', 'd'], 2)); return <div>App works</div>; } export default App;
解释 :使用
react-app-rewired
和customize-cra
覆盖默认配置,babel-plugin-lodash
优化 Lodash 的引入。
码字不易,大佬们点点赞