当前位置: 首页 > news >正文

useMemo vs useCallback:核心区别与使用场景

📌 一、useMemo:缓存计算结果

核心用途

避免在每次渲染时重复执行开销大的计算

典型使用场景

场景1:数据转换/过滤/排序

jsx

import { useMemo } from 'react'; function MeterList({ meters, filterType, searchText }) { // 不缓存:每次渲染都重新过滤 → 性能浪费 const visibleMeters = meters .filter(m => m.type === filterType) .filter(m => m.id.includes(searchText)); // ✅ 缓存:只在依赖变化时重新计算 const cachedMeters = useMemo(() => { console.log('执行复杂过滤...'); return meters .filter(m => m.type === filterType) .filter(m => m.id.includes(searchText)); }, [meters, filterType, searchText]); return ( <div> {cachedMeters.map(m => <MeterCard key={m.id} meter={m} />)} </div> ); }
场景2:复杂计算(统计分析)

jsx

function Dashboard({ readings }) { // ✅ 缓存统计结果 const statistics = useMemo(() => { // 假设这里有大量计算:总和、平均值、趋势分析... const total = readings.reduce((sum, r) => sum + r.value, 0); const avg = total / readings.length; const trend = calculateTrend(readings); // 复杂算法 return { total, avg, trend }; }, [readings]); return ( <div> <p>总用水量: {statistics.total}</p> <p>平均值: {statistics.avg}</p> </div> ); }
场景3:避免子组件不必要的重渲染(与React.memo配合)

jsx

const Chart = React.memo(({ data }) => { // 只有当data引用变化时才重绘 return <ExpensiveChart data={data} />; }); function Dashboard({ rawData }) { // ❌ 每次渲染都创建新对象 → Chart总是重绘 const chartData = { labels: rawData.map(d => d.time), values: rawData.map(d => d.value) }; // ✅ 缓存对象引用 → 只有rawData变化时才创建新对象 const cachedChartData = useMemo(() => ({ labels: rawData.map(d => d.time), values: rawData.map(d => d.value) }), [rawData]); return <Chart data={cachedChartData} />; }

📌 二、useCallback:缓存函数引用

核心用途

保持函数引用稳定,避免因函数重新创建导致子组件不必要的重渲染

典型使用场景

场景1:将回调传递给React.memo优化的子组件

jsx

import { useCallback, useState } from 'react'; // 子组件使用 React.memo const MeterButton = React.memo(({ onClick, label }) => { console.log('按钮渲染:', label); return <button onClick={onClick}>{label}</button>; }); function MeterPanel({ meterId }) { const [count, setCount] = useState(0); // ❌ 每次渲染都创建新函数 → MeterButton总是重绘 const handleClick = () => { console.log('点击了:', meterId); }; // ✅ 缓存函数引用 → 只有meterId变化时才创建新函数 const handleClickCached = useCallback(() => { console.log('点击了:', meterId); }, [meterId]); return ( <div> <p>计数: {count}</p> <MeterButton onClick={handleClickCached} label="点击我" /> <button onClick={() => setCount(c => c + 1)}>增加计数</button> </div> ); }
场景2:作为useEffect的依赖

jsx

function WaterMonitor({ stationId, onStatusChange }) { // ✅ 如果onStatusChange是稳定的,这个effect不会无限循环 useEffect(() => { const ws = new WebSocket(`ws://api/station/${stationId}`); ws.onmessage = (event) => { onStatusChange(JSON.parse(event.data)); }; return () => ws.close(); }, [stationId, onStatusChange]); // onStatusChange稳定,effect只执行一次 } function Parent() { const [status, setStatus] = useState(null); // ✅ 缓存回调,避免引起子组件的effect无限循环 const handleStatusChange = useCallback((data) => { setStatus(data); }, []); // 空依赖,永远不变 return <WaterMonitor stationId="123" onStatusChange={handleStatusChange} />; }
场景3:自定义Hook中返回函数

jsx

function useMeterActions(meterId) { const [data, setData] = useState(null); // ✅ 返回稳定的函数引用 const refresh = useCallback(async () => { const response = await fetch(`/api/meters/${meterId}`); const newData = await response.json(); setData(newData); }, [meterId]); const update = useCallback(async (updates) => { await fetch(`/api/meters/${meterId}`, { method: 'POST', body: JSON.stringify(updates) }); await refresh(); // 刷新数据 }, [meterId, refresh]); return { data, refresh, update }; }

🔍 三、何时使用?(决策指南)

优先使用useMemo的场景

  1. 计算开销大:遍历大数组、复杂数学计算、数据转换

  2. 创建对象/数组:作为props传递给React.memo子组件

  3. 派生状态:从props或state计算新值

优先使用useCallback的场景

  1. 传递给React.memo子组件的回调函数

  2. 作为自定义Hook的返回值(稳定API)

  3. 作为其他Hooks的依赖(如useEffect)

不需要优化的场景

jsx

// ✅ 简单事件处理,不需要缓存 function SimpleButton() { const handleClick = () => { console.log('点击了'); }; return <button onClick={handleClick}>点击</button>; } // ✅ JSX中直接使用箭头函数(如果子组件没有优化) <button onClick={() => doSomething(id)}>操作</button>

🎯 四、实战对比:智慧水务场景

让我们通过一个完整的例子来理解两者的区别:

jsx

import { useState, useMemo, useCallback } from 'react'; // 子组件 - 显示抄表记录 const ReadingTable = React.memo(({ records, onSelect }) => { console.log('ReadingTable 渲染'); return ( <table> {records.map(r => ( <tr key={r.id} onClick={() => onSelect(r)}> <td>{r.id}</td> <td>{r.value}</td> </tr> ))} </table> ); }); function WaterDashboard() { const [readings, setReadings] = useState([ { id: 1, value: 28, time: '09:12' }, { id: 2, value: 42, time: '10:45' }, // ...更多数据 ]); const [filter, setFilter] = useState('all'); const [selectedId, setSelectedId] = useState(null); // 1️⃣ useMemo: 缓存过滤后的数据 const filteredReadings = useMemo(() => { console.log('重新过滤数据...'); if (filter === 'all') return readings; return readings.filter(r => r.value > 30); }, [readings, filter]); // 2️⃣ useCallback: 缓存点击处理函数 const handleSelect = useCallback((record) => { console.log('选中:', record.id); setSelectedId(record.id); // 可能还有其他复杂逻辑 }, []); // 空依赖,因为setSelectedId是稳定的 // 3️⃣ 对比:如果不缓存 // const handleSelect = (record) => { // setSelectedId(record.id); // }; // ❌ 每次渲染都是新函数 → ReadingTable总是重绘 return ( <div> <select onChange={(e) => setFilter(e.target.value)}> <option value="all">全部</option> <option value="high">大于30</option> </select> {/* ReadingTable 只会在 filteredReadings 变化时重绘 */} <ReadingTable records={filteredReadings} onSelect={handleSelect} /> {selectedId && <div>当前选中: {selectedId}</div>} </div> ); }

⚠️ 五、常见陷阱

陷阱1:过度优化

jsx

// ❌ 不要这样做 - 简单计算不需要useMemo function add(a, b) { return a + b; } const result = useMemo(() => add(x, y), [x, y]); // 浪费! // ✅ 直接计算即可 const result = x + y;

陷阱2:依赖遗漏

jsx

function Component({ items, threshold }) { // ❌ 遗漏了threshold依赖 const filtered = useMemo(() => { return items.filter(item => item.value > threshold); }, [items]); // 当threshold变化时不会更新! // ✅ 正确做法 const filtered = useMemo(() => { return items.filter(item => item.value > threshold); }, [items, threshold]); }

陷阱3:函数内部的闭包问题

jsx

function Timer({ interval }) { const [count, setCount] = useState(0); // ❌ 闭包陷阱:callback中使用了过时的count const handleTick = useCallback(() => { setCount(count + 1); // count始终是初始值 }, []); // ✅ 正确:使用函数式更新 const handleTick = useCallback(() => { setCount(c => c + 1); }, []); useEffect(() => { const timer = setInterval(handleTick, interval); return () => clearInterval(timer); }, [interval, handleTick]); return <div>{count}</div>; }

📝 六、记忆口诀

  • useMemo:记住,避免重计算

  • useCallback:记住函数,避免重创建

  • 两个都依赖数组,依赖变时才更新

  • 配合React.memo使用,性能优化才完整

  • 不要过度优化,先测量再优化

记住这个简单的选择标准:如果你要缓存的是计算结果用useMemo,如果是函数本身用useCallback

http://www.jsqmd.com/news/495428/

相关文章:

  • ACDC变换器:单相PFC_Boost+后级半桥LLC,功率因素矫正及软开关技术实现(300W...
  • 2026年AI搜索优化公司深度测评:从技术到效果的客观分析与选型指南 - 小白条111
  • 麟智产业通,为您的企业数字化需求保驾护航
  • HCIP 路由控制 实验一
  • 2026年全国园林景观工程公司Top10,源石石业产品种类丰富吗 - myqiye
  • 2026年ASOC SCI2区TOP,基于人工蜂群算法和自适应大邻域搜索的星环网络设计问题求解方法,深度解析+性能实测
  • 论文排版之参考文献
  • KVM切换总黑屏、分辨率乱跳?问题可能出在EDID上|TESmart用这项技术彻底解决
  • 拖延症福音!全行业通用降AIGC工具 千笔·降AIGC助手 VS 灵感风暴AI
  • wsl安装包
  • 欧意注册okxz.run复制打开-2026年最新版V5.6.12.5.317安卓/苹果版
  • 2026必备!10个降AI率软件降AIGC网站评测:开源免费必看,学术降重全维度推荐
  • 看看众合食品包装吗,东三省食品包装口碑哪家好 - mypinpai
  • 热门的重庆包装袋制作制造企业
  • 2026年苏州纸箱加工优质厂家,定制适配方案兼顾多方面 - 工业设备
  • Kama缓存系统结果分析
  • 华硕电脑键盘全部失灵
  • 【Rust日报】 ry(o3) - Python的Rust封装库
  • hive—1.1、执行优化
  • 2026年镇江AI搜索优化公司推荐——从技术实力到效果落地的深度测评 - 小白条111
  • 程序员小白必看,三个月从零基础到实战,轻松转型大模型开发者!!
  • K8s Pod 网络策略与安全组配置
  • 2026年湖南光石传媒口碑TOP10,专业服务备受认可 - 工业推荐榜
  • 多尺度特征提取块改进YOLOv26空洞卷积与自适应权重融合双重突破
  • 2026 年 AI 产品经理薪资曝光:程序员转型能涨 40%?附完整学习体系 + 成功案例
  • 刘教链|比特观察:一个极致轻量的比特币观察钱包,带你回归聪本位
  • 2026年3月浙江浴室柜厂家权威评测:镱朗实业综合实力领跑 - 2026年企业推荐榜
  • 传统 A*算法与改进 A*算法性能大比拼及融合 DWA 避障仿真
  • WSL2+Ubuntu22.04+VScode开发环境
  • 2026年湖南光石性价比高吗?细聊售后完善的湖南光石口碑 - 工业品牌热点