React:常见的性能优化手段

React 性能优化

遇到性能问题时,不要一开始就盲目使用 memouseMemouseCallback。我们要先通过 React DevTools Profiler 找到真正的性能瓶颈,再进行针对性优化。

React 性能问题通常可以归纳为:

  • 不必要的重渲染
  • 单次渲染成本过高
  • DOM 节点过多
  • 更新频率过高
  • 首屏资源体积过大

本人推荐的教学视频:


一、减少不必要的重渲染:合理使用记忆化

使用 React.memo 包裹较重的子组件。当父组件重新渲染时,如果子组件的 props 没有发生变化(浅比较),React 可以跳过子组件的重新渲染。

对于复杂的数据,通常我们需要配合实现记忆化:

  • useMemo:保持数组、对象等引用稳定
  • useCallback:保持函数引用稳定
jsx 复制代码
const UserList = React.memo(function UserList({ users, onSelect }) {
  return users.map(user => (
    <div key={user.id} onClick={() => onSelect(user)}>
      {user.name}
    </div>
  ));
});

function App({ users }) {
  const visibleUsers = useMemo(
    () => users.filter(user => user.active),
    [users]
  );

  const handleSelect = useCallback((user) => {
    console.log(user);
  }, []);

  return (
    <UserList
      users={visibleUsers}
      onSelect={handleSelect}
    />
  );
}

需要注意的是,记忆化也有额外开销,不应为了"优化"而到处使用。只有在组件确实存在重复渲染成本时才值得使用。

我们也可以直接使用React Complier实现记忆化。

shell 复制代码
npm install -D babel-plugin-react-compiler@latest

二、合理拆分组件

合理建立状态边界和渲染边界:

  • state 放到真正使用它的最小组件中
  • 将频繁更新的部分拆分出来
  • 避免一个顶层组件管理过多状态
  • 让不相关的组件避免被连带重新渲染

例如,输入框的状态通常应该放在输入框组件内部,而不是放在整个页面的顶层组件中。

一个文件尽量只管理同一类职责的 state,例如表单数据由表单相关逻辑负责,加载状态由加载逻辑负责,避免不同类型的状态混杂在一起。


三、避免不必要的派生状态

如果一个值可以通过 props 或现有的 state 计算得到,就不应该再单独保存为 state

这样可以:

  • 减少状态数量
  • 避免状态不同步
  • 降低额外渲染次数
  • 保持单一真实数据来源
jsx 复制代码
function Cart({ items }) {
  const total = items.reduce(
    (sum, item) => sum + item.price * item.quantity,
    0
  );

  return <div>总价:{total}</div>;
}

如果计算成本较高,可以使用 useMemo

jsx 复制代码
const total = useMemo(() => {
  return calculateTotal(items);
}, [items]);

四、控制更新频率

1. 防抖 Debounce

防抖会等待一段时间,确认事件不再触发后才执行。

适合:

  • 搜索输入
  • 实时校验
  • 自动保存
  • 用户停止操作后再请求接口
js 复制代码
function debounce(fn, delay = 300) {
  let timer = null;

  function debounced(...args) {
    clearTimeout(timer);

    timer = setTimeout(() => {
      fn.apply(this, args);
    }, delay);
  }

  debounced.cancel = () => {
    clearTimeout(timer);
    timer = null;
  };

  return debounced;
}

2. 节流 Throttle

节流会限制函数在固定时间内最多执行一次。

适合:

  • 滚动事件
  • 鼠标移动
  • 窗口缩放
  • 拖拽操作
js 复制代码
function throttle(fn, interval = 100) {
  let lastTime = 0;
  let timer = null;

  function throttled(...args) {
    const now = Date.now();
    const remaining = interval - (now - lastTime);

    if (remaining <= 0) {
      clearTimeout(timer);
      timer = null;
      lastTime = now;
      fn.apply(this, args);
    } else if (!timer) {
      timer = setTimeout(() => {
        lastTime = Date.now();
        timer = null;
        fn.apply(this, args);
      }, remaining);
    }
  }

  return throttled;
}

3. 使用 useTransition

useTransition 不会减少实际计算量,它主要用于降低非紧急更新对用户交互的阻塞。将指定的状态更新标记为非紧急更新,优先保证输入等交互操作的流畅性。

适合:

  • 大量搜索结果渲染
  • 复杂列表筛选
  • 页面局部内容切换
jsx 复制代码
const [isPending, startTransition] = useTransition();

function handleChange(value) {
  setInputValue(value);

  startTransition(() => {
    setSearchKeyword(value);
  });
}

五、优化大量数据渲染:减少 DOM 数量

当页面需要渲染大量数据时,可以采用以下方式:

保证列表 key 稳定

渲染列表时,应使用数据中稳定且唯一的标识作为 key,例如id。

分页

只渲染当前页数据,减少一次性创建的 DOM 节点。

预加载

在用户即将进入下一页或接近列表底部时,提前请求数据,减少等待时间。

渐进式加载

先渲染核心内容,再逐步加载其他数据,避免页面一次性渲染过多内容。

虚拟化

虚拟化只渲染视口附近的元素,适合超长列表或表格。核心思想是只渲染当前视口附近的少量元素。

常见方案包括:

  • react-window
  • react-virtualized

六、懒加载与代码分割

使用 lazy 和动态 import 实现组件级代码分割,再通过 Suspense 展示加载状态。

jsx 复制代码
const UserPage = React.lazy(() => import('./UserPage'));

function App() {
  return (
    <Suspense fallback={<div>加载中...</div>}>
      <UserPage />
    </Suspense>
  );
}

适合懒加载的内容包括:

  • 设置页面
  • 管理后台
  • 富文本编辑器
  • 图表组件
  • 不常访问的功能模块

这样可以避免用户首次进入页面时一次性下载所有代码,降低首屏资源体积。

除了组件懒加载,还可以关注:

  • 路由级代码分割
  • 图片压缩和懒加载
  • 第三方依赖体积
  • Tree Shaking
  • SSR、流式渲染和缓存策略

七、合理划分状态管理边界

不同类型的状态,应采用不同的管理方式。

状态类型 推荐方案 典型场景
局部临时状态 useStateuseReducer 输入框、表单内容、弹窗状态
低频跨层级状态 Context 主题、语言、用户信息
复杂客户端状态 Zustand 等状态库 购物车、当前 AI 模型、编辑器实例
服务端状态 服务端与数据库 权限控制、多端同步、海量数据
  • 客户端状态:弹窗、表单、购物车、编辑器状态
  • 服务端状态(通常需要处理缓存、重新请求、错误、加载和数据同步):用户信息、订单、列表数据、缓存、请求状态

使用 Context 时的注意事项

Context 适合跨层级传递且更新频率较低的数据,但不建议将所有状态放入一个大型 Context 中。

否则可能导致:

  • Context 更新时造成非预期的重新渲染
  • 状态边界不清晰
  • 组件之间耦合增加
  • 后期维护困难

可以根据业务拆分不同的 Context,例如:

jsx 复制代码
<ThemeProvider>
  <AuthProvider>
    <App />
  </AuthProvider>
</ThemeProvider>

八、总结

React 性能优化的核心原则是:

  1. 先通过 Profiler 定位瓶颈,再进行优化
  2. 使用 memouseMemouseCallback 减少不必要的重渲染
  3. 通过合理拆分组件建立状态边界和渲染边界
  4. 避免保存不必要的派生状态
  5. 使用防抖、节流和 useTransition 控制更新频率
  6. 通过分页、渐进式加载和虚拟化减少 DOM 数量
  7. 使用懒加载降低首屏资源体积
  8. 根据状态的作用范围选择合适的状态管理方案
  9. 不要过早优化,避免增加不必要的复杂度

日常开发中,首先应遵循合理的 React 设计模式,写出结构清晰、状态边界合理的代码。只有在真实出现性能问题时,再根据 Profiler 的分析结果进行针对性优化。

相关推荐
天天码行空1 小时前
vkeyboardhand:零依赖虚拟键盘指法组件
前端·javascript·vue.js
东方小月1 小时前
从零开发一个 Coding Agent(五):使用 TypeBox 校验工具参数
前端·人工智能·后端
濮水大叔1 小时前
为什么 AI 最擅长 React/Next.js,却很少看到真正好用的 Next.js 开源项目?
react.js·node.js·next.js
我要两颗404西柚1 小时前
Stage four:VUE项目上线
前端·javascript·vue.js
尤乐娃子1 小时前
进入大厂(厂子大)实习Day8
前端·笔记·实习
醉城夜风~2 小时前
CSS盒模型(Box Model)
前端·css
雪隐2 小时前
个人电脑玩AI-15让5060 Ti给你打工——MiniMax H3 本地部署实录:一个自带录音棚的视频模型,和它的 NVFP4 瘦身奇遇
前端·人工智能·后端
GuWenyue3 小时前
不用第三方SDK!Vue3原生Fetch实现DeepSeek流式输出,90%前端都会踩的分片解析坑一次性解决
前端·人工智能·llm
独隅3 小时前
前端离线暂停更新策略:Service Worker 与 PWA 实战指南
前端