React 项目性能优化

该文章已生成可运行项目,

React 是构建现代 Web 应用的重要框架,但由于其响应式渲染机制,稍不注意就可能引发重复渲染、性能浪费、UI 卡顿等问题。

本文从组件设计、渲染控制、状态管理、Hooks 使用、异步优化、Suspense 并发特性、工具监控七大板块,大概讲解一下每一种优化方式。

一、组件层面的性能优化

1. 使用 React.memo 避免不必要的子组件更新

const MemoUserCard = React.memo(({ name }) => {
  console.log('MemoUserCard rendered');
  return <div>{name}</div>;
});

React.memo 是一个高阶组件,用于包裹函数组件,只有在 props 变更时才重新渲染。适合 UI 纯粹依赖 props 的子组件。

2. 拆分组件粒度,控制最小更新单元

将一个大型组件按功能拆成多个子组件,并使用 memo 或自定义渲染控制,能有效避免因为无关状态变化导致整体重渲。

const Dashboard = ({ user, chartData }) => (
  <>
    <MemoUserCard user={user} />
    <MemoChart data={chartData} />
  </>
);

二、渲染控制策略

1. 使用 key 优化列表渲染

list.map((item) => <li key={item.id}>{item.name}</li>);

key 是 React Diff 算法的核心,稳定的 key 能显著减少重建 DOM 的次数。

2. 使用 shouldComponentUpdate 或 PureComponent(已过时)

过去会使用 shouldComponentUpdatePureComponent 来控制类组件的重渲

class Profile extends React.PureComponent {
  render() {
    return <div>{this.props.name}</div>;
  }
}

在 Hooks 普及后,这些方式已逐渐退出主流开发实践

三、Hooks 使用注意事项

1. 使用 useMemo 缓存计算结果

const sortedList = useMemo(() => {
  return [...list].sort((a, b) => a.value - b.value);
}, [list]);

useMemo 可以避免每次渲染都重复计算。适合大数组排序、过滤等耗时逻辑。

2. 使用 useCallback 缓存函数引用

const handleClick = useCallback(() => console.log('Clicked'), []);

useCallback 可避免函数地址变化导致的子组件重新渲染,尤其是与 React.memo 结合使用时,能有效提升性能。

四、状态管理优化

1. 将状态放在真正需要它的组件中

避免将状态提升到不必要的父组件,从而引起子组件全部重渲。

// ❌ 所有子组件因 count 状态变化都被影响
const App = () => {
  const [count, setCount] = useState(0);
  return <Layout count={count} setCount={setCount} />;
};

// ✅ 仅 Counter 使用自身状态
const Counter = () => {
  const [count, setCount] = useState(0);
  return <button onClick={() => setCount(count + 1)}>{count}</button>;
};

2. 使用状态管理库(如 Zustand / Recoil)解耦依赖关系

Zustand 使用 selector(状态选择器)订阅状态,只有实际变化才更新组件,适合中大型项目性能控制。

// store.ts
import { create } from 'zustand';

const useStore = create(set => ({
  count: 0,
  increment: () => set(state => ({ count: state.count + 1 }))
}));

// Counter.tsx
const CountBtn = () => {
  const count = useStore(state => state.count); // ✅ 精准订阅
  const inc = useStore(state => state.increment);
  return <button onClick={inc}>Count: {count}</button>;
};

这边可以提一嘴Zustand 这个东西和我们平常用的useReducer有什么区别

useReducer 示例(适合组件内的复杂状态)

function reducer(state, action) {
  switch (action.type) {
    case 'increment':
      return { count: state.count + 1 };
    default:
      return state;
  }
}

function Counter() {
  const [state, dispatch] = useReducer(reducer, { count: 0 });

  return (
    <button onClick={() => dispatch({ type: 'increment' })}>
      Count: {state.count}
    </button>
  );
}

Zustand 示例(适合多个组件共享)

const useStore = create((set) => ({
  count: 0,
  increment: () => set((state) => ({ count: state.count + 1 })),
}));

function Counter() {
  const count = useStore((state) => state.count);
  const increment = useStore((state) => state.increment);
  return <button onClick={increment}>Count: {count}</button>;
}

function Total() {
  const count = useStore((state) => state.count); // 跨组件使用同一状态
  return <div>Total: {count}</div>;
}
对比项useReducerZustand
用法React 原生 Hook第三方库(基于 Hook 封装)
状态作用域💡 组件内部状态💡 跨组件、全局共享状态
更新方式通过 dispatch(action) 更新调用封装的 set 方法
状态结构单一 reducer 管理统一 state 对象可自由定义多个字段 + 方法(像 useState)
渲染性能每次更新都触发组件重渲染(全部)精准订阅某字段,局部更新(性能更好)
写法难度类似 Redux,需要定义 reducer、action写法简洁,像 useState 一样
Provider 需求不需要也不需要(Zustand 自动全局管理)
是否适合全局状态❌ 组件级状态✅ 全局状态场景理想选择

总结:
useReducer 是原生的、组件内状态管理方式,适合组件内部复杂状态变更;
Zustand 是更适合“全局状态管理”的工具,不需要 Context,也不需要 Redux,写法清晰、性能优秀。

五、异步请求与并发优化

1. 使用 AbortController 或请求标记避免竞态更新

useEffect(() => {
  const controller = new AbortController();
  const signal = controller.signal;

  fetch(`/api?q=${query}`, { signal })
    .then(res => res.json())
    .then(setData)
    .catch(err => {
      if (err.name !== 'AbortError') console.error(err);
    });

  return () => controller.abort(); // ✅ 中断旧请求
}, [query]);

当用户频繁变更搜索条件等请求参数时,老请求若未中止,可能覆盖新结果,造成竞态问题。(平常那种商城端项目用的比较多)

六、React Suspense 与并发特性(React 18)

1. 使用 React.lazy + Suspense 实现组件懒加载

const LazyChart = React.lazy(() => import('./Chart'));

<Suspense fallback={<div>加载中...</div>}>
  <LazyChart />
</Suspense>

减少首屏加载体积,按需加载非关键子组件资源。

2. 并发数据加载(还不稳定)

结合如 React Query、Relay 等库,可支持基于 Suspense 的数据并发加载:

<Suspense fallback={<Loading />}>
  <PostDetail id={postId} />
</Suspense>

七、工具与调试

1. 使用 React DevTools + Profiler 分析组件性能

  1. 开启 Profiler 插件
  2. 识别每次渲染组件、耗时
  3. 分析导致重新渲染的 state/props 来源

2. 使用 why-did-you-render 检查不必要渲染

import React from 'react';
if (process.env.NODE_ENV === 'development') {
  const whyDidYouRender = require('@welldone-software/why-did-you-render');
  whyDidYouRender(React);
}

MemoUserCard.whyDidYouRender = true; // 行代码的作用是:告诉 why-did-you-render:“请监听这个组件的渲染”。

总结

优化点技术方法
子组件重复渲染React.memo / useCallback
大计算逻辑useMemo
多函数回调useCallback
列表 diff稳定 key
状态影响范围大状态局部化 / 状态库(Zustand/Recoil)
异步竞态AbortController
首屏资源重React.lazy / Suspense
性能分析Profiler / why-did-you-render
本文章已经生成可运行项目
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值