一七一、React性能优化方式
在 React 中进行性能优化可以通过多种手段来减少渲染次数、优化渲染效率并减少内存消耗。以下是常见的性能优化方法及示例:
1. shouldComponentUpdate
shouldComponentUpdate
是类组件中的生命周期方法,它可以让组件在判断是否需要重新渲染时,避免不必要的渲染:
class MyComponent extends React.Component {
shouldComponentUpdate(nextProps, nextState) {
// 只有当 props 或 state 发生变化时才重新渲染
return nextProps.value !== this.props.value || nextState.count !== this.state.count;
}
render() {
return <div>{this.props.value}</div>;
}
}
在这个示例中,组件只有在 value
或 count
发生变化时才会重新渲染,这样可以避免不必要的渲染,提高性能。
2. PureComponent
PureComponent
是类组件的替代方案,使用浅层比较 props
和 state
来确定是否重新渲染。它相当于内置了 shouldComponentUpdate
的优化:
import React, { PureComponent } from 'react';
class MyPureComponent extends PureComponent {
render() {
return <div>{this.props.value}</div>;
}
}
PureComponent
自动实现了浅比较(shallow comparison),如果 props
和 state
没有变化,就不会触发重新渲染。适合用于不经常变化的静态内容。
3. React.memo
React.memo
是用于函数组件的高阶组件,类似于 PureComponent
,可以帮助防止不必要的渲染。React.memo
通过浅比较来优化函数组件的性能:
const MyFunctionalComponent = React.memo(({ value }) => {
return <div>{value}</div>;
});
使用 React.memo
包装后的组件,只有当 value
属性发生变化时才会重新渲染。如果有复杂的 props 检查逻辑,还可以自定义比较函数。
4. 避免使用内联函数
每次渲染时,内联函数会重新创建。可以将内联函数提取出来,减少函数的重新创建次数:
// 不推荐
<button onClick={() => console.log("clicked")}>Click</button>
// 推荐
const handleClick = () => console.log("clicked");
<button onClick={handleClick}>Click</button>
提取函数可以减少内存消耗,尤其在组件内频繁传递回调函数的场景下,可以减少不必要的性能开销。
5. 使用 React.Fragment
避免额外标记
React.Fragment
可以用来避免额外的 DOM 元素包裹,减少不必要的 DOM 结构,提升性能:
// 不推荐
<div>
<h1>Title</h1>
<p>Content</p>
</div>
// 推荐
<React.Fragment>
<h1>Title</h1>
<p>Content</p>
</React.Fragment>
或简写为 <></>
。这不仅优化了 DOM 结构,也减少了浏览器的渲染时间。
6. 使用 Immutable.js
或 immer
进行不可变数据操作
React 依赖 props
和 state
的不可变性来判断是否需要重新渲染。使用 Immutable.js
或 immer
可以确保数据的不可变性:
import { Map } from 'immutable';
const state = Map({ value: 1 });
const newState = state.set('value', 2); // 产生新的对象
// 或者使用 immer
import produce from 'immer';
const newState = produce(state, draft => {
draft.value = 2;
});
这种方式可以优化性能,避免重复的对象引用,保证更新的数据结构具有新引用,触发 React 的重新渲染。
7. 懒加载组件
懒加载可以减少初始加载的资源占用,仅在需要时加载组件。可以通过 React.lazy
和 Suspense
实现组件的懒加载:
import React, { Suspense, lazy } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
);
}
懒加载特别适合于大型应用,减少首屏渲染时间,同时将不需要的代码分批加载。
React.Suspense使用介绍
React.Suspense
是 React 用于管理组件加载状态的一个功能,它通常与懒加载组件和数据请求一起使用,确保应用程序在加载数据或组件时提供用户友好的过渡体验。以下是 React.Suspense
的基础介绍及其常见用法。
1. 基本使用
React.Suspense
通过 fallback
属性来指定加载中的占位内容(如加载动画或文本),在懒加载组件或数据尚未加载完成时,显示该占位内容:
import React, { Suspense, lazy } from 'react';
const LazyComponent = lazy(() => import('./LazyComponent'));
function App() {
return (
<div>
<Suspense fallback={<div>Loading...</div>}>
<LazyComponent />
</Suspense>
</div>
);
}
在此示例中,LazyComponent
会在需要时才被加载,而不是在应用程序加载时就加载。当 LazyComponent
还没加载完成时,fallback
中的内容 <div>Loading...</div>
会显示在页面上。
2. 配合懒加载 (React.lazy)
React.lazy
可以实现组件的动态导入。配合 Suspense
,可以实现按需加载,减少初始加载时间:
const LazyComponent = lazy(() => import('./LazyComponent'));
通过 lazy
将组件包装为懒加载模式,这样只有在组件被渲染时,LazyComponent
才会进行导入。
3. Suspense 与数据获取
自从 React 18
起,Suspense
也可以用来处理异步数据请求。在数据获取的场景下,可以与 React Server Components
或者像 React Query
这样的库结合使用。以下是一个简要示例:
import { Suspense } from 'react';
function DataFetchingComponent() {
const data = fetchData(); // 假设 fetchData 是一个获取数据的函数
return <div>{data}</div>;
}
function App() {
return (
<Suspense fallback={<div>Loading data...</div>}>
<DataFetchingComponent />
</Suspense>
);
}
在更复杂的应用中,可能会使用缓存库或 React
的新 useTransition
和 useDeferredValue
来进一步优化 Suspense 的效果和数据的呈现。
4. 多个 Suspense 组件
在复杂应用中,可能会嵌套或并列多个 Suspense
组件,以分阶段加载不同的部分。每个 Suspense
可以拥有不同的 fallback
内容,从而提供分区加载的用户体验:
function App() {
return (
<div>
<Suspense fallback={<div>Loading component A...</div>}>
<ComponentA />
</Suspense>
<Suspense fallback={<div>Loading component B...</div>}>
<ComponentB />
</Suspense>
</div>
);
}
5. 注意事项
- Error Boundaries:
Suspense
只能处理加载状态,但不处理错误。建议结合Error Boundaries
来捕获和处理可能发生的错误。 - 支持环境:确保 React 版本支持
Suspense
,特别是数据加载的Suspense
需要React 18
。
React.Suspense
的使用可以有效提高应用的响应速度和用户体验,特别是当组件和数据加载较多时。
8. 优化事件绑定方式
在组件上直接绑定事件会导致事件在每次渲染时都重新创建。可以通过在构造函数中绑定或使用 class fields 语法避免这个问题:
class MyComponent extends React.Component {
constructor() {
super();
this.handleClick = this.handleClick.bind(this); // 在构造函数中绑定事件
}
handleClick() {
console.log("clicked");
}
render() {
return <button onClick={this.handleClick}>Click</button>;
}
}
或者使用类字段语法:
class MyComponent extends React.Component {
handleClick = () => {
console.log("clicked");
};
render() {
return <button onClick={this.handleClick}>Click</button>;
}
}
这种方法减少了每次渲染时重新创建函数的性能开销。
9. 服务端渲染(Server-Side Rendering)
服务端渲染可以显著提高首屏渲染速度,尤其对 SEO 有较大帮助。使用 Next.js
等框架可以轻松实现 React 的服务端渲染:
// 使用 Next.js
import { useEffect } from 'react';
function MyComponent({ data }) {
return <div>{data}</div>;
}
export async function getServerSideProps() {
const data = await fetchData();
return { props: { data } };
}
export default MyComponent;
服务端渲染的页面在服务器上渲染成 HTML 返回客户端,客户端接收后进行静态内容显示并完成渲染后的数据填充,这样提高了用户的访问速度。