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

useCallback/useMemo

下面,我们来系统的梳理关于 React useCallback & useMemo 的基本知识点:


一、性能优化基础概念

1.1 React 渲染机制回顾

  • 渲染流程:组件状态/props变化 → 重新执行组件函数 → 生成新虚拟DOM → 与旧DOM对比 → 更新实际DOM
  • 性能瓶颈:不必要的重新渲染(re-render)和重复计算

1.2 引用相等性(Referential Equality)

const a = { value: 1 };
const b = { value: 1 };
console.log(a === b); // false - 不同内存引用const c = a;
console.log(a === c); // true - 相同引用
  • JavaScript中对象、数组、函数每次创建都是新引用
  • React中依赖引用相等性判断是否需要更新

二、useMemo 深度解析

2.1 基本概念

useMemo 缓存计算结果,避免每次渲染重复计算

2.2 核心语法

const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
  • 参数1:计算函数,返回需要缓存的值
  • 参数2:依赖数组,只有依赖变化时才重新计算

2.3 工作原理

graph LR
A[组件渲染] --> B{依赖项是否变化?}
B -->|是| C[重新执行计算函数]
B -->|否| D[返回缓存值]
C --> E[缓存新值并返回]

2.4 使用场景

场景1:昂贵计算缓存
function Chart({ data }) {// 避免每次渲染重新计算数据转换const transformedData = useMemo(() => {return transformData(data); // 耗时操作}, [data]);return <svg>{/* 使用 transformedData 渲染 */}</svg>;
}
场景2:避免子组件不必要重渲染
function Parent({ a, b }) {// 对象缓存,保证引用不变const config = useMemo(() => ({ color: 'red', size: a }), [a]);return <Child config={config} value={b} />;
}// 配合React.memo
const Child = React.memo(({ config, value }) => {/* 只在config或value变化时重渲染 */
});

2.5 注意事项

  • 不要滥用:计算不昂贵时,直接计算可能比useMemo开销更小
  • 依赖必须完整:遗漏依赖可能导致使用过期值
  • 副作用禁止:计算函数应为纯函数,无副作用

三、useCallback 深度解析

3.1 基本概念

useCallback 缓存函数引用,避免每次渲染创建新函数

3.2 核心语法

const memoizedCallback = useCallback(() => {doSomething(a, b);
}, [a, b]);
  • 参数1:需要缓存的函数
  • 参数2:依赖数组,依赖变化时返回新函数

3.3 与useMemo关系

// 两者等效
useCallback(fn, deps) === useMemo(() => fn, deps)

3.4 使用场景

场景1:避免子组件不必要重渲染
const Child = React.memo(({ onClick }) => {/* 依赖onClick */
});function Parent() {const [count, setCount] = useState(0);// 缓存函数引用const handleClick = useCallback(() => {console.log('点击');}, []); // 空依赖:始终相同return <Child onClick={handleClick} />;
}
场景2:作为其他Hook的依赖
function Search({ query }) {const [results, setResults] = useState([]);const fetchResults = useCallback(async () => {const data = await fetch(`/search?q=${query}`);setResults(await data.json());}, [query]); // query变化时重新创建useEffect(() => {fetchResults();}, [fetchResults]); // 依赖函数// ...
}

3.5 注意事项

  • 依赖处理:函数内部使用的state/props必须声明为依赖
  • 闭包陷阱:缓存函数捕获创建时的上下文
  • 必要性判断:子组件未用React.memo优化时无效

四、性能优化策略

4.1 何时使用 useMemo

  1. 计算成本高的操作(如大数据转换、复杂计算)
  2. 引用类型值作为props传递给优化组件
  3. 作为其他Hook的依赖项

4.2 何时使用 useCallback

  1. 函数作为props传递给优化组件
  2. 函数作为其他Hook的依赖项
  3. 事件处理函数被多次绑定(如列表项)

4.3 优化组件配合

// 使用React.memo进行组件记忆
const OptimizedComponent = React.memo(function Component(props) {/* 只在props变化时重渲染 */
});// 默认浅比较,可自定义比较函数
React.memo(Component, (prevProps, nextProps) => {/* 返回true不重渲染,false重渲染 */
});

五、高级模式

5.1 依赖项管理技巧

函数依赖稳定化
const [count, setCount] = useState(0);// 方法1:使用useCallback
const increment = useCallback(() => setCount(c => c + 1), []);// 方法2:使用dispatch(useReducer)
const increment = () => dispatch({ type: 'increment' });
对象依赖最小化
// 不推荐:依赖整个对象
useEffect(() => {}, [user]);// 推荐:只依赖必要属性
useEffect(() => {}, [user.id]);

5.2 自定义Hook封装

function useStableCallback(fn) {const ref = useRef();// 始终使用最新函数ref.current = fn;return useCallback((...args) => {return ref.current?.(...args);}, []);
}// 使用:避免依赖变化
const handleClick = useStableCallback(() => {console.log(props.data);
});

六、常见问题与解决方案

6.1 依赖循环问题

场景:useCallback依赖state,state更新触发函数重建,导致依赖该函数的useEffect循环执行

解决:使用函数式更新或useReducer

// 错误示例
const [data, setData] = useState([]);
const fetchData = useCallback(async () => {const res = await fetch('/api');setData(await res.json());
}, []); // 缺少setData依赖,但ESLint会警告// 正确方案1:使用函数式更新避免依赖
const fetchData = useCallback(async () => {const res = await fetch('/api');setData(prev => [...prev, ...await res.json()]);
}, []);// 正确方案2:使用useRef保存函数
const dataRef = useRef();
dataRef.current = data;
const fetchData = useCallback(async () => {// 使用dataRef.current
}, []);

6.2 闭包过期问题

场景:useCallback捕获创建时的旧状态

解决

const [count, setCount] = useState(0);// 方法1:使用函数式更新
const increment = useCallback(() => {setCount(prev => prev + 1);
}, []);// 方法2:使用ref保存最新值
const countRef = useRef(count);
countRef.current = count;const logCount = useCallback(() => {console.log(countRef.current);
}, []);

6.3 何时不用优化

  1. 简单组件:渲染成本低于优化开销
  2. 频繁变化的值:缓存意义不大
  3. 顶层组件:无父组件触发重渲染

七、案例

7.1 表单优化

function Form({ onSubmit }) {const [values, setValues] = useState({});// 缓存事件处理const handleChange = useCallback((e) => {setValues(v => ({ ...v, [e.target.name]: e.target.value }));}, []);// 缓存提交处理const handleSubmit = useCallback((e) => {e.preventDefault();onSubmit(values);}, [values, onSubmit]);return (<form onSubmit={handleSubmit}><input name="name" onChange={handleChange} /><input name="email" onChange={handleChange} /><button type="submit">提交</button></form>);
}

7.2 虚拟列表优化

function VirtualList({ items, itemHeight }) {const [scrollTop, setScrollTop] = useState(0);// 计算可见项(避免重复计算)const visibleItems = useMemo(() => {const startIndex = Math.floor(scrollTop / itemHeight);const endIndex = startIndex + 10;return items.slice(startIndex, endIndex);}, [items, scrollTop, itemHeight]);// 缓存滚动处理const handleScroll = useCallback((e) => {setScrollTop(e.currentTarget.scrollTop);}, []);return (<div className="list" onScroll={handleScroll}><div style={{ height: `${items.length * itemHeight}px` }}>{visibleItems.map(item => (<ListItem key={item.id} item={item} />))}</div></div>);
}

7.3 复杂状态派生

function Dashboard({ transactions }) {// 缓存复杂计算const summary = useMemo(() => {return transactions.reduce((acc, transaction) => {acc.total += transaction.amount;acc.categories[transaction.category] = (acc.categories[transaction.category] || 0) + transaction.amount;return acc;}, { total: 0, categories: {} });}, [transactions]);return (<div><TotalAmount value={summary.total} /><CategoryChart data={summary.categories} /></div>);
}

八、总结

8.1 决策流程图

graph TD
A[需要优化吗?] -->|是| B{优化什么?}
B -->|计算| C[useMemo]
B -->|函数| D[useCallback]
A -->|否| E[直接使用]

8.2 黄金法则

  1. 先测量后优化:使用React DevTools分析性能
  2. 避免过早优化:只在必要时使用
  3. 确保正确性:完整声明依赖项
  4. 组合优化:配合React.memo使用

8.3 性能优化全景图

优化技术适用场景工具
React.memo避免子组件不必要重渲染组件包装
useMemo缓存昂贵计算/稳定引用Hook
useCallback缓存函数引用Hook
虚拟化长列表渲染react-window
懒加载代码/组件延迟加载React.lazy
http://www.dtcms.com/a/300081.html

相关文章:

  • Item11:在operator=中处理自我赋值
  • [极客大挑战 2019]FinalSQL--布尔盲注
  • 【web应用】如何进行前后端调试Debug? + 前端JavaScript调试Debug?
  • 内置两大模型,Whisper视频语音转文字,支持批量处理,完全免费!
  • 车载诊断刷写 --- Flash关于擦除和写入大小
  • GStreamer中Element(元素)
  • sendfile系统调用及示例
  • Android 键盘
  • C# 位运算及应用
  • vulhub-earth靶机攻略
  • Day32| 509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯
  • 【硬件-笔试面试题】硬件/电子工程师,笔试面试题-31,(知识点:芯片容量,行地址,列地址,Bank地址,数据位宽,数据带宽)
  • SpringMVC——请求
  • 2025年全国青少年信息素养大赛Scratch算法创意实践挑战赛 小低组 初赛 真题
  • 深分页性能问题分析与优化实践
  • matplotlib库 点线图,直方图,多子图与三维空间的可视化
  • C++11语法
  • 计算机中的数据表示
  • C++ TAP(基于任务的异步编程模式)
  • 停止所有docker容器的命令
  • 【SSM】第二章 网上蛋糕项目商城-首页
  • 进程线程协程深度对比分析
  • 2025年渗透测试面试题总结-2025年HW(护网面试) 71(题目+回答)
  • HarmonyOS应用上架流程详解
  • element-plus安装以及使用
  • STM32概况
  • Matlab自学笔记六十五:解方程的数值解法(代码速成)
  • 如何查看电脑后门IP和流量?
  • ECSPI控制器
  • 【Spring AI】SiliconFlow-硅基流动