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

发布时间:2026/7/4 18:42:51

useMemo vs useCallback:核心区别与使用场景 一、useMemo缓存计算结果核心用途避免在每次渲染时重复执行开销大的计算。典型使用场景场景1数据转换/过滤/排序jsximport { 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复杂计算统计分析jsxfunction 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配合jsxconst 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优化的子组件jsximport { 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的依赖jsxfunction 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 stationId123 onStatusChange{handleStatusChange} /; }场景3自定义Hook中返回函数jsxfunction 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的场景计算开销大遍历大数组、复杂数学计算、数据转换创建对象/数组作为props传递给React.memo子组件派生状态从props或state计算新值优先使用useCallback的场景传递给React.memo子组件的回调函数作为自定义Hook的返回值稳定API作为其他Hooks的依赖如useEffect不需要优化的场景jsx// ✅ 简单事件处理不需要缓存 function SimpleButton() { const handleClick () { console.log(点击了); }; return button onClick{handleClick}点击/button; } // ✅ JSX中直接使用箭头函数如果子组件没有优化 button onClick{() doSomething(id)}操作/button 四、实战对比智慧水务场景让我们通过一个完整的例子来理解两者的区别jsximport { 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 valueall全部/option option valuehigh大于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依赖遗漏jsxfunction 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函数内部的闭包问题jsxfunction 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。

相关新闻