React 性能优化 之 合适的渲染策略
React 性能优化 是指通过采取一些 技术手段 来 减少 不必要的 渲染、提升 应用的 响应速度 和 用户体验。随着 React 应用复杂度 的增加,性能优化 变得尤为重要。React 提供了一些 内置的 工具 和 方法 来帮助开发者 优化性能。
1 避免 不必要的 重新渲染
1.1 使用 React.memo
React.memo
是一个 高阶组件(HOC),用于优化 函数组件。它通过对比 当前的 props
和 上次的 props
,决定是否重新 渲染组件。若 props
没有变化,则会 跳过重新渲染。
import React from 'react';
const MyComponent = React.memo(({ name }) => {
console.log('Rendering:', name);
return <h1>{name}</h1>;
});
function App() {
return (
<div>
<MyComponent name="Alice" />
<MyComponent name="Bob" />
</div>
);
}
export default App;
在这个例子中,只有 name
属性 变化时,MyComponent
才会 重新渲染。
1.2 使用 PureComponent
PureComponent
是一个 类组件,它只会在 props
或 state
改变时 才会重新渲染。与 React.memo
类似,PureComponent
内部使用 浅比较 来决定 是否更新。
import React, { PureComponent } from 'react';
class MyComponent extends PureComponent {
render() {
console.log('Rendering:', this.props.name);
return <h1>{this.props.name}</h1>;
}
}
function App() {
return (
<div>
<MyComponent name="Alice" />
<MyComponent name="Bob" />
</div>
);
}
export default App;
1.3 使用 shouldComponentUpdate
在 类组件 中,shouldComponentUpdate
方法 可以用来 手动控制组件 是否需要 重新渲染。如果返回 false
,则 React 会 跳过渲染。
import React, { Component } from 'react';
class MyComponent extends Component {
shouldComponentUpdate(nextProps) {
// 仅当 `name` 改变时才重新渲染
return nextProps.name !== this.props.name;
}
render() {
console.log('Rendering:', this.props.name);
return <h1>{this.props.name}</h1>;
}
}
function App() {
return (
<div>
<MyComponent name="Alice" />
<MyComponent name="Bob" />
</div>
);
}
export default App;
2 延迟渲染 和 代码分割
2.1 使用 React.lazy
和 <Suspense>
React.lazy
允许你 按需加载组件,只有当组件 实际被使用时 才加载。这对于 减少 初始加载时间 特别有效。
import React, { Suspense, lazy } from 'react';
// 使用 React.lazy 异步加载组件
const MyComponent = lazy(() => import('./MyComponent'));
function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<MyComponent />
</Suspense>
</div>
);
}
export default App;
Suspense
组件 用于 包裹 懒加载 的组件,可以在 加载过程中 显示一个 占位符(如 加载中的 提示)。
2.2 动态导入 与 代码分割
结合 React Router 和 动态导入,可以 按需加载 页面组件,避免 将整个应用 打包成一个 庞大的文件。
import React, { Suspense, lazy } from 'react';
import { BrowserRouter as Router, Route, Routes } from 'react-router-dom';
const Home = lazy(() => import('./Home'));
const About = lazy(() => import('./About'));
function App() {
return (
<Router>
<Suspense fallback={<div>Loading...</div>}>
<Routes>
<Route path="/" element={<Home />} />
<Route path="/about" element={<About />} />
</Routes>
</Suspense>
</Router>
);
}
export default App;
3 避免 重复渲染(避免 不必要的 props 传递)
按需更新 State
避免 在 useState
或 setState
中 更新 与当前状态 相同的值,React 会检测到 值没有变化,从而 跳过渲染。
import React, { useState } from 'react';
function App() {
const [count, setCount] = useState(0);
const increment = () => {
setCount((prevCount) => (prevCount === count ? prevCount : prevCount + 1));
};
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increment</button>
</div>
);
}
export default App;
4 使用 虚拟化列表(Virtualized List)
当 渲染 大量列表项 时,React 会为 每个列表项 都 渲染 一个组件。这会导致 性能问题。虚拟化 技术 可以只渲染 当前在屏幕上的 部分元素,从而 提升性能。
4.1 使用 react-window
或 react-virtualized
这两个库用于实现 虚拟化列表,能够显著提高 渲染 大规模数据 的效率。
import React from 'react';
import { FixedSizeList as List } from 'react-window';
function App() {
const items = new Array(1000).fill(null).map((_, index) => `Item ${index + 1}`);
return (
<List
height={500}
itemCount={items.length}
itemSize={35}
width={300}
>
{({ index, style }) => (
<div style={style}>{items[index]}</div>
)}
</List>
);
}
export default App;
5 使用 合适的 渲染策略
5.1 使用 useMemo
和 useCallback
useMemo
:用于 缓存 计算结果,避免 重复计算;useCallback
:用于 缓存 函数实例,避免 不必要的 函数重建。
// 示例:使用 `useMemo` 缓存计算结果
import React, { useState, useMemo } from 'react';
function App() {
const [count, setCount] = useState(0);
const expensiveComputation = useMemo(() => {
console.log('Expensive computation running...');
return count * 2;
}, [count]);
return (
<div>
<p>Count: {count}</p>
<p>Expensive Computation: {expensiveComputation}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
</div>
);
}
export default App;
// 示例:使用 `useCallback` 缓存回调函数
import React, { useState, useCallback } from 'react';
function Child({ onClick }) {
console.log('Child rendered');
return <button onClick={onClick}>Click me</button>;
}
function App() {
const [count, setCount] = useState(0);
const increment = useCallback(() => {
setCount((prevCount) => prevCount + 1);
}, []);
return (
<div>
<p>Count: {count}</p>
<Child onClick={increment} />
</div>
);
}
export default App;
6 合理使用 Context
React Context
是一种 传递 全局状态 的方法,但如果传递的 数据频繁变化,可能导致 所有使用该 Context 的组件 重新渲染,影响性能。为了解决这个问题,可以 分层 或 细化 Context
的使用,使其只影响 需要的部分。
6.1 优化 Context 的使用
通过将多个 独立的状态 分割 到 不同的 Context 中,减少 不必要的渲染。
import React, { createContext, useState, useContext } from 'react';
// 分割 Context
const CountContext = createContext();
const NameContext = createContext();
function App() {
const [count, setCount] = useState(0);
const [name, setName] = useState('Alice');
return (
<CountContext.Provider value={{ count, setCount }}>
<NameContext.Provider value={{ name, setName }}>
<Child />
</NameContext.Provider>
</CountContext.Provider>
);
}
function Child() {
const { count, setCount } = useContext(CountContext);
const { name, setName } = useContext(NameContext);
return (
<div>
<p>Count: {count}</p>
<p>Name: {name}</p>
<button onClick={() => setCount(count + 1)}>Increment</button>
<button onClick={() => setName('Bob')}>Change Name</button>
</div>
);
}
export default App;