aurora blog
  • JS

    • 基本汇总
    • Date时间
    • Array方法
    • String 方法
    • Object方法
    • RegExp正则
    • Es6 新特性等
    • Event-Loop
    • Http and Https
    • for in和for of区别
    • Web Worker
    • Promise && async
    • 堆内存 & 栈内存
    • JS设计模式探索
    • npm & yarn
    • Fetch和Axios的区别
    • 学习及面试问题整理
    • URL 输入到页面加载过程
    • 跨域&nginx本地项目代理
  • FE框架

    • react

      • 基本用法
      • react生命周期
      • hooks 16.8版本后
      • Route原理
      • Redux源码解析
      • React16 Fiber
      • React-VirtualDOM
      • React事件委托机制
      • React性能优化
      • 状态管理方案对比
      • React 18新特性
    • vue

      • vue 基本用法
      • vue 生命周期
      • VirtualDOM
      • vue 事件委托
      • vue 架构
      • vue 状态管理
      • 问题等
    • React-Native

      • React-Native 基本用法
    • 微前端

      • 遇到的问题
    • 鸿蒙 harmony

      • harmony 基础
  • 构建工具

    • webpack

      • Webpack配置详解
      • Webpack的使用
      • Babel-polyfill
      • webpack面试题
    • vite

      • vite配置详解
      • vite面试题
    • rollup

      • Rollup配置详解
      • rollup面试题
    • 构建工具对比
  • 性能优化

    • 性能优化策略
    • 缓存策略
    • 性能优化面试题
  • 浏览器

    • 浏览器渲染原理
    • 浏览器缓存机制
    • 浏览器面试题
  • 工程化

    • 代码规范
    • 工程化面试题
  • 前端安全

    • XSS 攻击与防护
    • CSRF 攻击与防护
    • 前端安全面试题
  • 移动端开发

    • 移动端适配
    • 移动端开发面试题
  • 前端监控

    • 错误监控
    • 性能监控
    • 前端监控面试题
  • Typescript

    • Typescript详解
  • Servers

    • Nodejs
    • Nginx
  • Git命令

    • git常用规范
  • 数据库

    • mongodb
    • mongodb
  • Other

    • Jenkins自动化部署

React 性能优化

  • 性能优化概述
  • 减少不必要的渲染
    • 1. React.memo
    • 2. useMemo
    • 3. useCallback
    • 4. 避免在 JSX 中创建对象和函数
  • 列表优化
    • 1. 正确使用 key
    • 2. 虚拟滚动
    • 3. 分页和懒加载
  • 代码分割
    • 1. 路由懒加载
    • 2. 组件懒加载
    • 3. 动态导入
  • 状态管理优化
    • 1. 状态提升和降级
    • 2. Context 优化
  • 其他优化技巧
    • 1. 避免内联样式对象
    • 2. 使用生产版本
    • 3. 使用 React DevTools Profiler
    • 4. 避免在 render 中执行副作用
    • 5. 使用 Web Workers 处理耗时任务
  • 性能监控
    • 1. 使用 React Profiler API
    • 2. 性能指标
  • 最佳实践总结
    • 1. 组件层面
    • 2. 列表优化
    • 3. 代码分割
    • 4. 状态管理
    • 5. 工具使用
    • 6. 注意事项

性能优化概述

React 性能优化的核心目标是:

  • 减少不必要的渲染
  • 优化渲染性能
  • 减少包体积
  • 提升用户体验

减少不必要的渲染

1. React.memo

作用: 对组件进行浅比较,避免不必要的重新渲染

使用:

// 基础用法
const MyComponent = React.memo(function MyComponent({ name }) {
  return <div>{name}</div>;
});

// 自定义比较函数
const MyComponent = React.memo(
  function MyComponent({ user }) {
    return <div>{user.name}</div>;
  },
  (prevProps, nextProps) => {
    // 返回 true 表示 props 相同,不重新渲染
    return prevProps.user.id === nextProps.user.id;
  }
);

适用场景:

  • 组件 props 变化不频繁
  • 组件渲染成本较高
  • 父组件频繁重新渲染

注意:

  • 只做浅比较
  • 不要过度使用,比较本身也有成本

2. useMemo

作用: 缓存计算结果,避免重复计算

使用:

function ExpensiveComponent({ items, filter }) {
  // ❌ 不好:每次渲染都重新计算
  const filteredItems = items.filter(item => item.type === filter);
  
  // ✅ 好:使用 useMemo 缓存
  const filteredItems = useMemo(() => {
    return items.filter(item => item.type === filter);
  }, [items, filter]);
  
  return <div>{filteredItems.map(item => <Item key={item.id} item={item} />)}</div>;
}

适用场景:

  • 昂贵的计算
  • 依赖项变化不频繁
  • 计算结果用于子组件 props

注意:

  • 不要过度使用
  • 比较依赖项也有成本

3. useCallback

作用: 缓存函数引用,避免子组件不必要的重新渲染

使用:

function Parent({ items }) {
  const [count, setCount] = useState(0);
  
  // ❌ 不好:每次渲染都创建新函数
  const handleClick = (id) => {
    console.log('clicked', id);
  };
  
  // ✅ 好:使用 useCallback 缓存函数
  const handleClick = useCallback((id) => {
    console.log('clicked', id);
  }, []); // 依赖项为空,函数引用不变
  
  return (
    <div>
      <button onClick={() => setCount(count + 1)}>Count: {count}</button>
      {items.map(item => (
        <Child key={item.id} item={item} onClick={handleClick} />
      ))}
    </div>
  );
}

const Child = React.memo(({ item, onClick }) => {
  return <div onClick={() => onClick(item.id)}>{item.name}</div>;
});

适用场景:

  • 函数作为 props 传递给子组件
  • 子组件使用 React.memo 优化
  • 函数依赖项变化不频繁

注意:

  • 不要过度使用
  • 确保依赖项正确

4. 避免在 JSX 中创建对象和函数

问题代码:

function Component({ items }) {
  return (
    <div>
      {/* ❌ 每次渲染都创建新对象 */}
      <Child style={{ color: 'red' }} />
      
      {/* ❌ 每次渲染都创建新函数 */}
      {items.map(item => (
        <Item key={item.id} onClick={() => handleClick(item.id)} />
      ))}
    </div>
  );
}

优化:

function Component({ items }) {
  // ✅ 提取到组件外部或使用 useMemo
  const style = useMemo(() => ({ color: 'red' }), []);
  
  // ✅ 使用 useCallback
  const handleItemClick = useCallback((id) => {
    handleClick(id);
  }, []);
  
  return (
    <div>
      <Child style={style} />
      {items.map(item => (
        <Item key={item.id} onClick={handleItemClick} itemId={item.id} />
      ))}
    </div>
  );
}

列表优化

1. 正确使用 key

问题:

// ❌ 不好:使用 index 作为 key
{items.map((item, index) => (
  <Item key={index} item={item} />
))}

优化:

// ✅ 好:使用唯一 ID
{items.map(item => (
  <Item key={item.id} item={item} />
))}

原因:

  • index 作为 key 会导致组件状态混乱
  • 列表顺序变化时性能问题
  • 可能导致不必要的重新渲染

2. 虚拟滚动

适用场景: 长列表渲染

实现:

import { FixedSizeList } from 'react-window';

function VirtualList({ items }) {
  return (
    <FixedSizeList
      height={600}
      itemCount={items.length}
      itemSize={50}
      width="100%"
    >
      {({ index, style }) => (
        <div style={style}>
          {items[index].name}
        </div>
      )}
    </FixedSizeList>
  );
}

优势:

  • 只渲染可见区域
  • 减少 DOM 节点
  • 提升滚动性能

3. 分页和懒加载

分页:

function PaginatedList({ items }) {
  const [page, setPage] = useState(1);
  const pageSize = 20;
  
  const paginatedItems = useMemo(() => {
    const start = (page - 1) * pageSize;
    return items.slice(start, start + pageSize);
  }, [items, page]);
  
  return (
    <div>
      {paginatedItems.map(item => (
        <Item key={item.id} item={item} />
      ))}
      <button onClick={() => setPage(page + 1)}>Next</button>
    </div>
  );
}

懒加载:

import { useState, useEffect, useRef } from 'react';

function LazyList({ items }) {
  const [visibleItems, setVisibleItems] = useState(items.slice(0, 20));
  const observerRef = useRef();
  
  useEffect(() => {
    const observer = new IntersectionObserver((entries) => {
      if (entries[0].isIntersecting) {
        setVisibleItems(prev => {
          const nextIndex = prev.length;
          return [...prev, ...items.slice(nextIndex, nextIndex + 20)];
        });
      }
    });
    
    observerRef.current = observer;
    return () => observer.disconnect();
  }, [items]);
  
  return (
    <div>
      {visibleItems.map(item => (
        <Item key={item.id} item={item} />
      ))}
      <div ref={observerRef} />
    </div>
  );
}

代码分割

1. 路由懒加载

使用 React.lazy:

import { lazy, Suspense } from 'react';
import { BrowserRouter, Routes, Route } from 'react-router-dom';

const Home = lazy(() => import('./pages/Home'));
const About = lazy(() => import('./pages/About'));

function App() {
  return (
    <BrowserRouter>
      <Suspense fallback={<div>Loading...</div>}>
        <Routes>
          <Route path="/" element={<Home />} />
          <Route path="/about" element={<About />} />
        </Routes>
      </Suspense>
    </BrowserRouter>
  );
}

2. 组件懒加载

按需加载组件:

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

function App() {
  const [showHeavy, setShowHeavy] = useState(false);
  
  return (
    <div>
      <button onClick={() => setShowHeavy(true)}>Load Heavy</button>
      {showHeavy && (
        <Suspense fallback={<div>Loading...</div>}>
          <HeavyComponent />
        </Suspense>
      )}
    </div>
  );
}

3. 动态导入

使用 import():

function loadModule() {
  return import('./module').then(module => {
    module.doSomething();
  });
}

状态管理优化

1. 状态提升和降级

状态提升:

// ❌ 不好:状态在子组件
function Parent() {
  return <Child />;
}

function Child() {
  const [count, setCount] = useState(0);
  return <div>{count}</div>;
}

// ✅ 好:状态提升到父组件
function Parent() {
  const [count, setCount] = useState(0);
  return <Child count={count} />;
}

状态降级:

// ❌ 不好:状态在父组件,导致所有子组件重新渲染
function Parent() {
  const [count, setCount] = useState(0);
  return (
    <div>
      <ExpensiveChild1 />
      <ExpensiveChild2 />
      <button onClick={() => setCount(count + 1)}>{count}</button>
    </div>
  );
}

// ✅ 好:状态降级到子组件
function Parent() {
  return (
    <div>
      <ExpensiveChild1 />
      <ExpensiveChild2 />
      <Counter />
    </div>
  );
}

2. Context 优化

问题: Context 值变化会导致所有消费者重新渲染

优化:

// ❌ 不好:值对象每次都是新的
function App() {
  const [user, setUser] = useState({ name: 'John' });
  
  return (
    <UserContext.Provider value={{ user, setUser }}>
      <Child />
    </UserContext.Provider>
  );
}

// ✅ 好:分离 Context 或使用 useMemo
function App() {
  const [user, setUser] = useState({ name: 'John' });
  
  const value = useMemo(() => ({ user, setUser }), [user]);
  
  return (
    <UserContext.Provider value={value}>
      <Child />
    </UserContext.Provider>
  );
}

// ✅ 更好:分离 Context
const UserContext = createContext();
const SetUserContext = createContext();

function App() {
  const [user, setUser] = useState({ name: 'John' });
  
  return (
    <UserContext.Provider value={user}>
      <SetUserContext.Provider value={setUser}>
        <Child />
      </SetUserContext.Provider>
    </UserContext.Provider>
  );
}

其他优化技巧

1. 避免内联样式对象

// ❌ 不好
<div style={{ color: 'red' }} />

// ✅ 好:使用 CSS 类
<div className="red-text" />

// ✅ 或提取样式对象
const style = { color: 'red' };
<div style={style} />

2. 使用生产版本

# 开发版本包含警告和调试信息,体积大
# 生产版本已优化,体积小

# 构建生产版本
npm run build

3. 使用 React DevTools Profiler

分析组件渲染:

  1. 打开 React DevTools
  2. 切换到 Profiler 标签
  3. 点击录制
  4. 执行操作
  5. 停止录制
  6. 分析性能瓶颈

4. 避免在 render 中执行副作用

// ❌ 不好:在 render 中执行副作用
function Component() {
  fetchData(); // 每次渲染都执行
  return <div>...</div>;
}

// ✅ 好:使用 useEffect
function Component() {
  useEffect(() => {
    fetchData();
  }, []);
  return <div>...</div>;
}

5. 使用 Web Workers 处理耗时任务

function Component() {
  const [result, setResult] = useState(null);
  
  useEffect(() => {
    const worker = new Worker('worker.js');
    
    worker.postMessage({ data: largeData });
    
    worker.onmessage = (e) => {
      setResult(e.data);
    };
    
    return () => worker.terminate();
  }, []);
  
  return <div>{result}</div>;
}

性能监控

1. 使用 React Profiler API

import { Profiler } from 'react';

function onRenderCallback(id, phase, actualDuration) {
  console.log('Component:', id);
  console.log('Phase:', phase);
  console.log('Duration:', actualDuration);
}

function App() {
  return (
    <Profiler id="App" onRender={onRenderCallback}>
      <Component />
    </Profiler>
  );
}

2. 性能指标

关键指标:

  • FCP (First Contentful Paint):首次内容绘制
  • LCP (Largest Contentful Paint):最大内容绘制
  • FID (First Input Delay):首次输入延迟
  • TTI (Time to Interactive):可交互时间

最佳实践总结

1. 组件层面

  • 使用 React.memo 优化组件
  • 使用 useMemo 缓存计算结果
  • 使用 useCallback 缓存函数引用
  • 避免在 JSX 中创建对象和函数

2. 列表优化

  • 正确使用 key(使用唯一 ID)
  • 长列表使用虚拟滚动
  • 分页和懒加载

3. 代码分割

  • 路由懒加载
  • 组件懒加载
  • 动态导入

4. 状态管理

  • 合理提升和降级状态
  • Context 优化(分离或使用 useMemo)

5. 工具使用

  • React DevTools Profiler
  • React Profiler API
  • 性能监控指标

6. 注意事项

  • 不要过度优化
  • 先测量再优化
  • 关注用户体验
  • 平衡性能和可维护性
最近更新:: 2025/11/20 14:50
Contributors: liyulai
Prev
React事件委托机制
Next
状态管理方案对比