React性能优化

React应用在复杂场景下容易出现渲染性能瓶颈,合理优化能显著提升用户体验。React性能优化手段的核心在于减少不必要的渲染、控制资源加载和合理使用缓存机制。

1. 使用 React.memo 避免子组件无意义重渲染

当父组件更新时,即使子组件props未变,也会默认重新渲染。React.memo可缓存组件输出,仅在props变化时重新更新。

示例Demo:

javascript 复制代码
import React, { useState } from "react";

const ExpensiveComponent = React.memo(({ count }) => {
  console.log("ExpensiveComponent 渲染了");
  return <div>计算结果: {count * 100}</div>;
});

function App() {
  const [count, setCount] = useState(0);
  const [toggle, setToggle] = useState(false);

  return (
    <div style={{ padding: "20px", fontFamily: "sans-serif" }}>
      <h2>React.memo 避免子组件重渲染</h2>
      <button onClick={() => setCount((c) => c + 1)}>增加计数</button>
      <button onClick={() => setToggle((t) => !t)}>切换状态</button>
      <p>当前计数: {count}</p>
      <ExpensiveComponent count={count} />
      <p style={{ marginTop: "16px" }}>
        切换状态不会触发 ExpensiveComponent 重渲染
      </p>
    </div>
  );
}

export default App;

附上codesandbox链接:react-memo
关键点:React.memo默认做浅比较,适用于props为基本类型或不变对象的场景。

2. 使用 useMemo 缓存昂贵计算

对复杂计算(如过滤、排序、数学运算)使用useMemo避免每次渲染都重新执行。

示例Demo:

javascript 复制代码
import React, { useState, useMemo } from "react";

function ExpensivePrimeCounter() {
  const [limit, setLimit] = useState(0);
  const [toggle, setToggle] = useState(false);

  // 模拟昂贵计算:计算小于 limit 的质数个数
  const primeCount = useMemo(() => {
    console.log("✅ 重新计算质数个数(未被缓存)");
    let count = 0;
    for (let i = 2; i <= limit; i++) {
      let isPrime = true;
      for (let j = 2; j * j <= i; j++) {
        if (i % j === 0) {
          isPrime = false;
          break;
        }
      }
      if (isPrime) count++;
    }
    return count;
  }, [limit]); // 仅当 limit 变化时重新计算

  return (
    <div
      style={{
        padding: "2rem",
        fontFamily: "sans-serif",
        maxWidth: "500px",
        margin: "0 auto",
      }}
    >
      <h2>质数计数器</h2>
      <p>
        当前上限:<strong>{limit}</strong>
      </p>
      <p>
        小于 {limit} 的质数个数:<strong>{primeCount}</strong>
      </p>
      <button
        onClick={() => setLimit((c) => c + 1)}
        style={{ margin: "0.5rem" }}
      >
        增加上限
      </button>
      <button onClick={() => setToggle((t) => !t)}>切换状态</button>
      <p style={{ fontSize: "0.8rem", color: "#666" }}>
        注意:仅当上限变化时,质数计算才会重新执行 ------ 缓存生效!
      </p>
    </div>
  );
}

export default ExpensivePrimeCounter;

附上codesandbox链接:react-useMemo
注意:不要滥用useMemo,仅对计算成本高的场景使用(如1000+条数据处理)

3. 使用 useCallback 避免回调函数重新创建

回调函数作为props传递给子组件时,每次渲染都会生成新函数,导致React.memo失效。

示例Demo:

javascript 复制代码
import React from "react";

const Button = React.memo(({ onClick, label }) => {
  console.log("Button rendered");
  return <button onClick={onClick}>{label}</button>;
});

export default function Parent() {
  const [count, setCount] = React.useState(0);

  // ❌ 每次渲染都创建新函数 → Button 会重新渲染
  // const handleClick = () => setCount(c => c + 1);

  // ✅ 使用 useCallback 缓存函数引用
  const handleClick = React.useCallback(() => {
    setCount((c) => c + 1);
  }, []);

  return (
    <div>
      <p>Count: {count}</p>
      <Button label="Increment" onClick={handleClick} />
    </div>
  );
}

附上codesandbox链接:react-useCallback

useCallback与React.memo配合使用,是优化组件树的关键组合。

4. 虚拟滚动:渲染大量列表时只渲染可见项

对长列表(如1000+项)使用虚拟滚动,避免DOM节点爆炸。

javascript 复制代码
import React from 'react';
import { FixedSizeList as List } from 'react-window';

const Row = ({ index, style }) => (
  <div style={style}>
    Item {index}
  </div>
);

function LongList({ items }) {
  return (
    <List
      height={400}
      itemCount={items.length}
      itemSize={35}
      width="100%"
    >
      {Row}
    </List>
  );
}

需安装依赖:react-window或react-virtualized

性能提升:从渲染1000个DOM节点 → 仅渲染10~20个可见节点

5. 懒加载组件:按需加载非关键模块

使用React.lazy + Suspense实现代码分割,减少首屏加载体积。

javascript 复制代码
import React, { lazy, Suspense } from 'react';

const HeavyComponent = lazy(() => import('./HeavyComponent'));

function App() {
  return (
    <div>
      <h1>Dashboard</h1>
      <Suspense fallback={<div>Loading...</div>}>
        <HeavyComponent />
      </Suspense>
    </div>
  );
}

适用于:弹窗、配置页、图标组件等非首屏内容

可结合webpack实现自动代码分割,SSR模式下可以使用@loadable/component

6. 使用React DevTools分析渲染性能

安装 React DevTools 浏览器插件,启用"Highlight updates when components render"功能,直观看到哪些组件被不必要地重渲染。

总结

优化手段 适用场景 是否推荐
React.memo 子组件props稳定 强烈推荐
useMemo 复杂计算、昂贵函数 推荐
useCallback 回调传给memoized子组件 推荐
虚拟滚动 长列表(>500项) 必须使用
React.lazy/@loadable/component 非首屏组件 推荐

不要过早优化。先保证功能正确,再用工具定位瓶颈,针对性优化。

相关推荐
福兮说4 小时前
设计稿是 #4A7C6F,页面量出来是 #4B7C6F:HEX、HSL、透明度、canvas 来回转的七个坑
前端·javascript·css·canvas
郑州光合科技余经理5 小时前
海外版外卖加盟:总站与分站配送规则怎么分开管
java·开发语言·前端·后端·uni-app·php·ai编程
小呆呆6665 小时前
副业搞起来,小说,漫画,漫剧的成本优化思路
前端·后端·面试
Dovis(誓平步青云)6 小时前
浇水提醒刚弹出又消失,植物状态别只存一个百分比
开发语言·前端·javascript·pdf·ecmascript·电脑
码艺-Alimjan6 小时前
Vben Admin 新增维吾尔语 Vben-Modal的关键坑之一
前端·javascript·vue.js
可乐鸡翅yeah_7 小时前
hls.js 手动自定义 http 请求 loader,修改请求头实战
开发语言·前端·javascript·网络协议·http·ecmascript·m3u8在线
IT_陈寒7 小时前
Vite静态资源导入这个坑我帮你们踩过了
前端·人工智能·后端
广州华水科技7 小时前
大坝安全监测解决方案:单北斗GNSS形变监测系统应用与维护
前端
Alice-YUE7 小时前
RAF 渲染帧速通:从渲染帧到性能优化
性能优化
yivifu8 小时前
中文古籍电子书注释集成
前端·javascript·python·beautifulsoup·epub