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

04-性能优化与最佳实践——03. useCallback - 函数缓存

03. useCallback - 函数缓存

一、5W1H 概述

维度内容
What缓存函数引用,避免函数在每次渲染时重新创建
Why配合 React.memo 优化子组件渲染,作为 useEffect 依赖
When函数传递给 memo 化的子组件、作为 useEffect 依赖
Where函数组件顶层
Who需要优化函数引用的开发者
Howconst memoizedCallback = useCallback(() => fn(), [deps])

二、What - 什么是 useCallback?

useCallback 是一个用于缓存函数引用的 Hook,返回一个记忆化的回调函数。

import { useCallback } from 'react'; const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]);

三、Why - 为什么需要 useCallback?

3.1 避免子组件不必要的重渲染

配合 React.memo 使用时,如果函数引用每次都变化,memo 优化会失效。

// ❌ 每次渲染都创建新函数,导致 Child 重渲染 function Parent() { const handleClick = () => console.log('click'); return <Child onClick={handleClick} />; } // ✅ 使用 useCallback 稳定函数引用 function Parent() { const handleClick = useCallback(() => console.log('click'), []); return <Child onClick={handleClick} />; }

3.2 作为 useEffect 的依赖

当函数作为 useEffect 依赖时,需要稳定引用避免 effect 频繁执行。


四、When - 何时使用 useCallback?

场景是否使用说明
传递给 memo 化的子组件✅ 推荐避免子组件重渲染
作为 useEffect 依赖✅ 推荐避免 effect 频繁执行
普通函数(未传递)❌ 不推荐优化成本大于收益
传递给未 memo 的组件❌ 不推荐没有优化效果

五、Where - 在哪里使用?

  • 函数组件的顶层
  • 自定义 Hook 中
// ✅ 正确:组件顶层 function MyComponent() { const handleClick = useCallback(() => {}, []); } // ✅ 正确:自定义 Hook function useCustomHandler() { const handler = useCallback(() => {}, []); return handler; }

六、Who - 谁需要使用?

需要优化性能、避免不必要重渲染的 React 开发者。


七、How - 如何使用 useCallback?

7.1 基础示例

import { useCallback, useState } from 'react'; function Parent() { const [count, setCount] = useState(0); const [text, setText] = useState(''); // ❌ 每次渲染都创建新函数 const handleClick = () => { console.log('点击了', count); }; // ✅ 只在 count 变化时创建新函数 const memoizedHandleClick = useCallback(() => { console.log('点击了', count); }, [count]); return ( <div> <Child onClick={memoizedHandleClick} /> <button onClick={() => setCount(count + 1)}>增加计数</button> <input value={text} onChange={(e) => setText(e.target.value)} /> </div> ); } const Child = React.memo(({ onClick }) => { console.log('Child 渲染'); return <button onClick={onClick}>点击</button>; });

7.2 传递给 memo 子组件

const TodoItem = React.memo(({ todo, onToggle, onDelete }) => { console.log(`TodoItem ${todo.id} 渲染`); return ( <li> <input type="checkbox" checked={todo.completed} onChange={() => onToggle(todo.id)} /> <span>{todo.text}</span> <button onClick={() => onDelete(todo.id)}>删除</button> </li> ); }); function TodoList() { const [todos, setTodos] = useState([ { id: 1, text: '学习 React', completed: false }, { id: 2, text: '学习 useCallback', completed: false } ]); // ✅ 缓存回调函数,避免子组件不必要的重渲染 const handleToggle = useCallback((id) => { setTodos(prev => prev.map(todo => todo.id === id ? { ...todo, completed: !todo.completed } : todo ) ); }, []); // 依赖为空,因为使用了函数式更新 const handleDelete = useCallback((id) => { setTodos(prev => prev.filter(todo => todo.id !== id)); }, []); return ( <ul> {todos.map(todo => ( <TodoItem key={todo.id} todo={todo} onToggle={handleToggle} onDelete={handleDelete} /> ))} </ul> ); }

7.3 作为 useEffect 依赖

function SearchComponent() { const [query, setQuery] = useState(''); const [results, setResults] = useState([]); // ✅ 缓存函数,避免 useEffect 频繁执行 const search = useCallback(async (searchQuery) => { const response = await fetch(`/api/search?q=${searchQuery}`); const data = await response.json(); setResults(data); }, []); useEffect(() => { if (query) { search(query); } }, [query, search]); // search 稳定,不会导致无限循环 return ( <div> <input value={query} onChange={(e) => setQuery(e.target.value)} /> <ul>{results.map(item => <li key={item.id}>{item.name}</li>)}</ul> </div> ); }

7.4 带参数的函数

function ItemList({ items }) { // 方式1:在回调中传递参数 const handleClick = useCallback((id) => { console.log('点击了', id); }, []); // 方式2:使用>7.5 表单处理
function Form() { const [formData, setFormData] = useState({ name: '', email: '', message: '' }); // 通用的字段更新函数 const updateField = useCallback((field) => (e) => { setFormData(prev => ({ ...prev, [field]: e.target.value })); }, []); const handleSubmit = useCallback((e) => { e.preventDefault(); console.log('提交:', formData); }, [formData]); return ( <form onSubmit={handleSubmit}> <input value={formData.name} onChange={updateField('name')} placeholder="姓名" /> <input value={formData.email} onChange={updateField('email')} placeholder="邮箱" /> <textarea value={formData.message} onChange={updateField('message')} placeholder="消息" /> <button type="submit">提交</button> </form> ); }

7.6 防抖和节流

function DebouncedSearch() { const [searchTerm, setSearchTerm] = useState(''); const [results, setResults] = useState([]); // 实际的搜索函数 const performSearch = useCallback(async (query) => { const response = await fetch(`/api/search?q=${query}`); const data = await response.json(); setResults(data); }, []); // 防抖版本 const debouncedSearch = useCallback( debounce((query) => { if (query) performSearch(query); }, 500), [performSearch] ); const handleChange = useCallback((e) => { const value = e.target.value; setSearchTerm(value); debouncedSearch(value); }, [debouncedSearch]); return ( <div> <input value={searchTerm} onChange={handleChange} placeholder="搜索..." /> <ul>{results.map(item => <li key={item.id}>{item.name}</li>)}</ul> </div> ); }

八、常见陷阱

8.1 依赖数组错误

function BadCallback() { const [count, setCount] = useState(0); // ❌ 缺少 count 依赖,函数内使用的是闭包中的旧值 const handleClick = useCallback(() => { console.log(count); // 永远打印 0 }, []); // ✅ 正确包含依赖 const handleClickCorrect = useCallback(() => { console.log(count); }, [count]); }

8.2 与 React.memo 配合不当

const Child = React.memo(({ onClick, data }) => { // ... }); function Parent() { const [text, setText] = useState(''); // ❌ 每次渲染都创建新函数,导致 Child 重渲染 const handleClick = () => { console.log('click'); }; // ✅ 缓存函数,避免 Child 重渲染 const memoizedClick = useCallback(() => { console.log('click'); }, []); return ( <div> <Child onClick={memoizedClick} data={data} /> <input value={text} onChange={(e) => setText(e.target.value)} /> </div> ); }

8.3 在循环中使用 useCallback

function ItemList({ items }) { // ❌ 为每个 item 创建单独的 useCallback(不必要) const handlers = items.map(item => ({ onEdit: useCallback(() => editItem(item.id), []), onDelete: useCallback(() => deleteItem(item.id), []) })); // ✅ 使用单个回调,通过参数区分 const handleEdit = useCallback((id) => { editItem(id); }, []); const handleDelete = useCallback((id) => { deleteItem(id); }, []); return ( <ul> {items.map(item => ( <li key={item.id}> <button onClick={() => handleEdit(item.id)}>编辑</button> <button onClick={() => handleDelete(item.id)}>删除</button> </li> ))} </ul> ); }

九、useCallback vs useMemo

// useCallback 缓存函数 const fn = useCallback(() => { doSomething(a, b); }, [a, b]); // useMemo 缓存函数(等价写法) const fn = useMemo(() => { return () => doSomething(a, b); }, [a, b]); // 何时使用: // - useCallback: 缓存回调函数 // - useMemo: 缓存计算结果

十、练习题

基础题

  1. 使用 useCallback 优化传递给子组件的回调
  2. 实现一个 Todo 列表,使用 useCallback 优化添加、删除、切换操作

进阶题

  1. 实现一个搜索组件,使用 useCallback 配合防抖
  2. 实现一个表单,使用 useCallback 优化字段更新函数

十一、小结

要点说明
主要用途传递给 memo 子组件、作为其他 Hook 依赖
不适用场景普通函数、未优化的子组件
依赖数组必须正确声明所有依赖
性能权衡useCallback 本身也有开销

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

相关文章:

  • 树结构Steklov特征值最大化:从双蜘蛛图到广义跷跷板树
  • 原来还有这么靠谱的TPU热熔胶膜公司?究竟好在哪?
  • DonkeyCar油门校准实战指南:从PWM信号到精准扭矩控制
  • 第 31 篇:keep-alive:连接保活的真相
  • 台球辅助工具终极指南:3分钟掌握精准瞄准技巧
  • Hokuyo激光雷达与gmapping建图原理及TurtleBot实战调优
  • 终极指南:3步安装League Akari,免费英雄联盟智能助手提升你的游戏体验
  • GEO内容结构化技术是什么?如何让AI精准提取和引用品牌信息?
  • 3步搭建个人专属网页邮箱:Roundcube Mail完整实战指南
  • 1个脚本搞定5个网盘签到
  • 【6.17】搞懂 OFDM:5G、WiFi 高速上网的底层核心,顺带讲清它天生的 “音量忽大忽小” 毛病!
  • 8位MCU市场格局与技术演进:从历史洞察看嵌入式控制器的持久生命力
  • Windows资源管理器3D模型缩略图革命:告别“盲选文件“,开启可视化文件管理新时代
  • MHMarkets迈汇:“算力热潮支撑市场情绪”
  • How to Write a Strong Thesis Statement
  • 美加墨世界杯期间,请网站防范风险插件造成的劫持
  • 099、NPU的RISC-V扩展:自定义NPU指令
  • 【维安康】射频功率放大器:全链条自主可控,重新定义无线通信的“能量引擎“
  • 孟献贵民法精讲讲义2026年|孟献贵民法精讲讲义2026答案|孟献贵民法精讲讲义
  • AI/ML论文的Thesis Statement写作指南:从模糊描述到可证伪的技术主张
  • 04-性能优化与最佳实践——05. 代码分割 - lazy 与 Suspense
  • Mythos能力解析:隐性知识建模与跨语境前提推演技术
  • ORM(Object-Relational Mapping,对象关系映射)
  • Lingjing(灵境)+vulnhub:Empire_Breakout打靶记录
  • 监督对比学习提升木薯病害识别准确率的实战解析
  • 别把 AI 硬塞进 OA:从审批、问答到数据分析的落地清单
  • 李佳行政法笔记|李佳行政法精讲讲义|李佳行政法口诀
  • 092、NPU的虚拟地址支持:MMU与IOMMU
  • 孟献贵民法精讲pdf|孟献贵民法视频|孟献贵民法口诀
  • AI这缸中之脑如何触碰现实? AI 的“脑机接口”Function Call