react常用优化手段

一、渲染优化(解决 "重复渲染、无效渲染",最核心)

React 性能问题 80% 来自不必要的组件重渲染,这是优化的首要方向:

1. 避免组件无意义重渲染
(1)基础版:React.memo + useCallback + useMemo
  • React.memo:缓存函数组件,仅当 props 浅对比变化时才重渲染(类组件用PureComponent/shouldComponentUpdate);
  • useCallback:缓存函数引用,避免因函数重新创建导致子组件重渲染;
  • useMemo:缓存计算结果 / 组件,避免重复计算 / 重复渲染。

示例代码

jsx

javascript 复制代码
import { useState, memo, useCallback, useMemo } from 'react';

// 子组件:用React.memo缓存,仅props变化时重渲染
const Child = memo(({ onClick, data }) => {
  console.log('Child 重渲染');
  return <button onClick={onClick}>{data.name}</button>;
});

function Parent() {
  const [count, setCount] = useState(0);
  const [user] = useState({ name: 'React' });

  // 缓存函数:避免每次渲染创建新函数,导致Child重渲染
  const handleClick = useCallback(() => {
    console.log('点击');
  }, []); // 依赖为空,永久缓存

  // 缓存复杂数据:避免每次渲染重新生成,导致Child重渲染
  const memoizedData = useMemo(() => ({ ...user }), [user]);

  return (
    <div>
      <p>count: {count}</p>
      <button onClick={() => setCount(count + 1)}>count+1</button>
      {/* 即使count变化,Child也不会重渲染 */}
      <Child onClick={handleClick} data={memoizedData} />
    </div>
  );
}

核心说明

  • 若不用React.memo+useCallback,点击count+1时,Parent 重渲染会创建新的handleClick函数,导致 Child 也跟着重渲染;
  • 仅对频繁重渲染的组件使用这些 API,普通组件无需过度封装(有微小性能开销)。
(2)进阶版:避免传递 "动态对象 / 数组" 作为 props

jsx

javascript 复制代码
// 错误写法:每次渲染创建新对象,触发子组件重渲染
<Child filter={{ type: 'all', status: 1 }} />

// 正确写法:缓存动态对象
const filter = useMemo(() => ({ type: 'all', status: 1 }), []);
<Child filter={filter} />
2. 精准控制状态作用域
  • 把状态放在最小必要组件中,避免状态提升过高导致无关组件重渲染;
  • 示例:搜索框的输入状态只放在搜索组件内,而非页面根组件,避免整个页面因输入框变化重渲染。
3. 虚拟列表(解决长列表卡顿)

当列表数据超过 100 条时,渲染所有 DOM 会导致卡顿,用「虚拟列表」只渲染可视区域的 DOM:

  • 推荐库:react-window/react-virtualized(轻量选前者,功能全选后者)。

示例(react-window)

jsx

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

// 长列表数据(比如10000条)
const listData = Array.from({ length: 10000 }, (_, i) => `Item ${i}`);

function LongList() {
  // 只渲染可视区域的item
  const Row = ({ index, style }) => (
    <div style={style}>{listData[index]}</div>
  );

  return (
    <List
      height={500} // 可视区域高度
      width="100%" // 宽度
      itemCount={listData.length} // 总数据量
      itemSize={50} // 每个item的高度
    >
      {Row}
    </List>
  );
}

二、加载优化(解决 "首屏慢、白屏时间长")

1. 路由懒加载(分割代码,减小首屏包体积)

React.lazy + Suspense实现路由级别的代码分割,首屏只加载当前路由的代码:

示例代码

jsx

javascript 复制代码
import { BrowserRouter, Routes, Route, Suspense } from 'react-router-dom';
// 加载中占位组件
const Loading = () => <div>加载中...</div>;

// 懒加载路由组件(分割代码)
const Home = React.lazy(() => import('./pages/Home'));
const About = React.lazy(() => import('./pages/About'));
const User = React.lazy(() => import('./pages/User'));

function App() {
  return (
    <BrowserRouter>
      {/* Suspense包裹懒加载组件,指定加载中UI */}
      <Suspense fallback={<Loading />}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
          <Route path="/user" element={<User />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}
2. 资源预加载 / 预获取
  • 预加载(Preload):加载当前页面必需的资源(比如关键 CSS、字体);
  • 预获取(Prefetch):空闲时加载未来可能用到的资源(比如下一个路由的代码)。

示例(HTML 中配置)

html 复制代码
<!-- 预加载关键字体 -->
<link rel="preload" href="/fonts/iconfont.woff2" as="font" type="font/woff2" crossorigin>
<!-- 预获取About页面的代码包(假设打包后文件名是about.js) -->
<link rel="prefetch" href="/static/js/about.js">
3. 图片优化
  • imgloading="lazy"实现图片懒加载(仅滚动到可视区域才加载);
  • 用 WebP/AVIF 等现代图片格式,减小体积;
  • 适配不同屏幕:用srcset + sizes加载不同分辨率的图片。

示例

jsx

javascript 复制代码
<img
  src="/images/photo-480w.jpg"
  srcset="/images/photo-480w.jpg 480w, /images/photo-800w.jpg 800w"
  sizes="(max-width: 600px) 480px, 800px"
  loading="lazy"
  alt="示例图片"
/>

三、打包优化(减小构建包体积,提升加载速度)

基于 Webpack/Vite(React 主流构建工具)的优化技巧:

1. 分析包体积,定位大依赖
  • Webpack:用webpack-bundle-analyzer生成包体积分析图;
  • Vite:用rollup-plugin-visualizer

使用示例(Webpack)

js

javascript 复制代码
// webpack.config.js
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;

module.exports = {
  plugins: [
    new BundleAnalyzerPlugin() // 启动后自动打开分析页面
  ]
};

运行npm run build后,会看到哪些依赖体积大(比如 lodash、moment.js),针对性优化。

2. 按需引入第三方库
  • 避免全量引入:比如lodash只引入需要的方法,antd/Element UI开启按需加载。

示例

jsx

javascript 复制代码
// 错误:全量引入lodash(体积大)
import _ from 'lodash';

// 正确:只引入需要的方法
import debounce from 'lodash/debounce';

// antd按需加载(需配置babel-plugin-import)
import { Button, Table } from 'antd';
3. 替换大体积依赖
  • 用轻量库替代:比如moment.js(体积大)→ dayjs/date-fns(体积小,按需引入);
  • 示例:import dayjs from 'dayjs' 替代 import moment from 'moment'
4. 压缩代码和资源
  • Webpack:用TerserPlugin压缩 JS,css-minimizer-webpack-plugin压缩 CSS;
  • 开启 Gzip/Brotli 压缩(后端 Nginx 配置,或前端构建时生成压缩包)。

四、通用优化(其他易落地的小技巧)

1. 减少 DOM 操作
  • 避免频繁修改 DOM:用 React 状态驱动视图,而非直接操作 DOM;
  • 长列表用 Fragment(<>)包裹,避免多余的父节点。
2. 防抖 / 节流处理高频事件

比如搜索框输入、窗口 resize、滚动事件,避免频繁触发函数:

示例(防抖)

jsx

javascript 复制代码
import { useState, useCallback } from 'react';
import debounce from 'lodash/debounce';

function Search() {
  const [keyword, setKeyword] = useState('');

  // 防抖:输入停止500ms后才执行搜索
  const handleSearch = useCallback(
    debounce((val) => {
      console.log('搜索:', val);
      // 发请求逻辑
    }, 500),
    []
  );

  return (
    <input
      type="text"
      value={keyword}
      onChange={(e) => {
        setKeyword(e.target.value);
        handleSearch(e.target.value);
      }}
      placeholder="请输入搜索关键词"
    />
  );
}
3. 合理使用缓存
  • 接口数据缓存:用useState+useEffect缓存请求结果,或用SWR/React Query(自动缓存、重试、失效);
  • 本地缓存:常用数据存localStorage/sessionStorage,避免重复请求。

示例(SWR 缓存接口数据)

jsx

javascript 复制代码
import useSWR from 'swr';

// 封装请求函数
const fetcher = (url) => fetch(url).then(res => res.json());

function UserList() {
  // SWR自动缓存数据,重新进入页面无需重复请求
  const { data, error } = useSWR('/api/users', fetcher);

  if (error) return <div>加载失败</div>;
  if (!data) return <div>加载中</div>;
  return <div>{data.map(user => <p key={user.id}>{user.name}</p>)}</div>;
}
4. 避免内存泄漏
  • 清理副作用:比如定时器、事件监听、请求取消,在useEffect的返回函数中销毁;

示例

jsx

javascript 复制代码
useEffect(() => {
  const timer = setInterval(() => {
    console.log('定时器执行');
  }, 1000);

  // 组件卸载时清理定时器
  return () => clearInterval(timer);
}, []);

总结

  1. 渲染优化 是核心:优先用React.memo+useCallback+useMemo避免无效重渲染,长列表用虚拟列表;
  2. 加载优化提体验:路由懒加载、图片懒加载、资源预加载,减小首屏加载时间;
  3. 打包优化减体积:分析包体积、按需引入依赖、替换大体积库,降低资源加载耗时;
  4. 通用优化补细节:防抖节流、合理缓存、清理副作用,避免性能问题和内存泄漏。

优化的核心原则是「先定位问题,再针对性优化」------ 先用 Chrome DevTools 的 Performance 面板分析卡顿原因,用 Network 面板分析加载问题,再选择对应的技巧落地,避免无意义的过度优化。

相关推荐
攀登的牵牛花2 小时前
前端向架构突围系列 - 框架设计(六):解析接口职责的单一与隔离
前端·架构
涵涵(互关)2 小时前
JavaScript 对大整数(超过 2^53 - 1)的精度丢失问题
java·javascript·vue.js
开开心心_Every2 小时前
离线黑白照片上色工具:操作简单效果逼真
java·服务器·前端·学习·edge·c#·powerpoint
Mintopia2 小时前
🌌 信任是否会成为未来的货币?
前端·人工智能·aigc
fqbqrr2 小时前
2601C++,模块导出分类
前端·c++
卓码软件测评2 小时前
软件首版次认定测试机构:【Apifox与UMI框架结合:实现OpenAPI规范与Mock服务的自动化流水线】
测试工具·ci/cd·性能优化·单元测试·测试用例
浮游本尊2 小时前
React 18.x 学习计划 - 第十二天:企业级实践与进阶主题
学习·react.js·状态模式
lili-felicity2 小时前
React Native 鸿蒙跨平台开发:useColorScheme 自定义鸿蒙端深色模式的主题配色
react native·react.js·harmonyos
倚栏听风雨2 小时前
vscode 运用 ts 代码需要准备什么
前端