React之useMeno

介绍

useMemo 是 React 中的一个 Hook,用于优化性能,通过缓存计算结果来避免不必要的重复计算。它适用于需要复杂计算的场景,或者需要保持引用稳定性的场景。

使用场景

  1. 复杂计算的优化
  2. 避免子组件不必要的重渲染(结合 React.memo 使用)
  3. 保持对象/数组引用的稳定性

代码示例

ini 复制代码
import React, { useMemo, useState } from 'react';

const ExpensiveCalculation = () => {
  const [count, setCount] = useState(0);
  const [multiplicand, setMultiplicand] = useState(1);

  // 未优化的昂贵计算(每次渲染都会执行)
  // const result = count * 1000 * Math.random(); // ❌ 性能隐患

  // 使用useMemo优化后的计算(仅当count变化时重新计算)
  const result = useMemo(() => {
    console.log('执行昂贵计算...');
    // 模拟复杂计算:时间复杂度 O(n^2)
    let sum = 0;
    for(let i = 0; i < 10000; i++) {
      for(let j = 0; j < 10000; j++) {
        sum += i * j * count;
      }
    }
    return sum;
  }, [count]); // ✅ 只在count变化时重新计算

  return (
    <div>
      <h2>计算结果: {result}</h2>
      <button onClick={() => setCount(c => c + 1)}>
        改变依赖值 (+1)
      </button>
      
      <input 
        value={multiplicand}
        onChange={(e) => setMultiplicand(Number(e.target.value))}
        placeholder="输入不影响计算的数值"
      />
      
      <p>无关状态值: {multiplicand}</p>
    </div>
  );
}

示例说明:

  1. 当点击"改变依赖值"按钮时,count变化触发重新计算
  2. 修改输入框数值时(改变multiplicand),不会触发昂贵计算
  3. 控制台可见"执行昂贵计算..."只在count变化时输出

进阶使用:优化组件渲染

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

// 用户列表组件
const UserList = ({ users, filterText }) => {
  // 使用useMemo缓存过滤结果
  const filteredUsers = useMemo(() => {
    console.log('执行用户过滤...');
    return users.filter(user => 
      user.name.toLowerCase().includes(filterText.toLowerCase())
    );
  }, [users, filterText]); // 依赖users数组和过滤文本

  return (
    <ul>
      {filteredUsers.map(user => (
        <li key={user.id}>{user.name} - {user.email}</li>
      ))}
    </ul>
  );
}

// 父组件
const UserDashboard = () => {
  const [users, setUsers] = useState([]);
  const [filterText, setFilterText] = useState('');
  const [refreshCount, setRefreshCount] = useState(0);

  // 模拟API请求
  useEffect(() => {
    fetchUsers().then(data => setUsers(data));
  }, [refreshCount]);

  return (
    <div>
      <input
        value={filterText}
        onChange={(e) => setFilterText(e.target.value)}
        placeholder="搜索用户..."
      />
      
      <button onClick={() => setRefreshCount(c => c + 1)}>
        刷新列表(共刷新 {refreshCount} 次)
      </button>
      {/* 优化的列表组件 */}
      <UserList users={users} filterText={filterText} />
    </div>
  );
}

优化效果:

  1. 输入过滤文本时:仅当users或filterText变化时重新过滤
  2. 点击刷新按钮时:users数组更新触发重新过滤
  3. 父组件其他状态变化不会导致过滤计算重复执行

与 useCallback 的区别

特性 useMemo useCallback
返回值 缓存的计算结果 缓存的函数引用
典型使用场景 避免昂贵计算重复执行 防止子组件不必要的重新渲染
等价写法 useMemo(() => fn, deps) useCallback(fn, deps)
内存占用 存储计算结果 存储函数对象

转换示例:

scss 复制代码
// 这两个写法完全等效
const memoizedCallback = useCallback(() => {
  doSomething(a, b);
}, [a, b]);

// useMemo实现相同功能
const memoizedCallback = useMemo(() => {
  return () => {
    doSomething(a, b);
  };
}, [a, b]);

最佳实践原则

  1. 按需使用原则
scss 复制代码
// 不需要缓存的情况(简单计算)
const total = items.length; // ✅ 直接使用
// 需要缓存的情况(复杂计算)
const total = useMemo(() => calculateSum(items), [items]); // ✅
  1. 依赖项精确原则
scss 复制代码
const user = useMemo(() => ({
  name: firstName + ' ' + lastName,
  age: currentAge
}), [firstName, lastName, currentAge]); // ✅ 精确依赖

// 错误示例(遗漏依赖)
const user = useMemo(() => ({
  name: firstName + ' ' + lastName,
}), [firstName]); // ❌ lastName变化时不会更新
  1. 配合React.memo使用
javascript 复制代码
const MemoizedComponent = React.memo(({ data }) => {
  /* 渲染逻辑 */
});

const Parent = () => {
  const processedData = useMemo(() => process(rawData), [rawData]);
  
  return <MemoizedComponent data={processedData} />
}

性能监控示例

ini 复制代码
// 创建一个带有性能测量的useMemo
function useProfiledMemo(factory, deps) {
  const start = performance.now();
  const value = useMemo(factory, deps);
  const end = performance.now();
  
  console.log(`计算耗时: ${(end - start).toFixed(2)}ms`);
  return value;
}

// 使用示例
const result = useProfiledMemo(() => {
  // 复杂计算...
}, [deps]);

常见误区解析

  1. 滥用缓存
scss 复制代码
// 错误:缓存简单计算反而更慢
const value = useMemo(() => 1 + 1, []); // ❌ 毫无意义
  1. 依赖项缺失
scss 复制代码
const [count, setCount] = useState(0);

const double = useMemo(() => {
  return count * 2; // ❌ 缺少count依赖
}, []); // 当count变化时不会更新
  1. 副作用滥用
scss 复制代码
// 错误:在useMemo中执行副作用
const data = useMemo(() => {
  fetchData(); // ❌ 副作用应放在useEffect中
  return processData();
}, []);

适用场景总结

场景 示例 收益程度
复杂计算/数据转换 数组过滤、排序、数学计算 ⭐️⭐️⭐️⭐️⭐️
大列表渲染 虚拟列表的可见项计算 ⭐️⭐️⭐️⭐️
避免子组件不必要渲染 配合React.memo使用 ⭐️⭐️⭐️⭐️
稳定引用(对象/数组) 防止useEffect重复触发 ⭐️⭐️⭐️
依赖第三方库计算 使用lodash进行复杂数据操作 ⭐️⭐️⭐️⭐️
ini 复制代码
// 稳定对象引用示例
const config = useMemo(() => ({
  color: theme === 'dark' ? '#fff' : '#000',
  fontSize: 16,
  responsive: true
}), [theme]); // 当theme变化时才生成新对象
相关推荐
魔云连洲3 小时前
详细解释浏览器是如何渲染页面的?
前端·css·浏览器渲染
Kx…………3 小时前
Day2—3:前端项目uniapp壁纸实战
前端·css·学习·uni-app·html
培根芝士5 小时前
Electron打包支持多语言
前端·javascript·electron
mr_cmx5 小时前
Nodejs数据库单一连接模式和连接池模式的概述及写法
前端·数据库·node.js
东部欧安时6 小时前
研一自救指南 - 07. CSS面向面试学习
前端·css
涵信6 小时前
第十二节:原理深挖-React Fiber架构核心思想
前端·react.js·架构
ohMyGod_1236 小时前
React-useRef
前端·javascript·react.js
每一天,每一步6 小时前
AI语音助手 React 组件使用js-audio-recorder实现,将获取到的语音转成base64发送给后端,后端接口返回文本内容
前端·javascript·react.js
上趣工作室6 小时前
vue3专题1------父组件中更改子组件的属性
前端·javascript·vue.js
冯诺一没有曼6 小时前
无线网络入侵检测系统实战 | 基于React+Python的可视化安全平台开发详解
前端·安全·react.js