04-性能优化与最佳实践——03. useCallback - 函数缓存
2026/6/25 12:33:00 网站建设 项目流程

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 本身也有开销

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询