在React王国,每次组件重新渲染,所有在组件中定义的函数都会被重新创建。这就像每天早上醒来,你都要重新学习如何刷牙一样荒谬!然而,这正是React组件的默认行为。
useCallback是React提供的"记忆魔法",它让函数可以被"记住",避免在每次渲染时创建新函数,从而减少子组件不必要的重新渲染。
想象你在一家大公司工作:
没有useCallback的世界:每天早上保安都会销毁你昨天的ID卡,然后发给你一张新的,虽然长得一模一样。每次门禁系统(React子组件)都会说:“这是新卡,需要重新检查”—即使这张卡的权限完全相同!
使用useCallback的世界:保安知道,只有当你的权限变化(依赖项变化)时,才需要发新卡。其他时候,你可以继续使用原来的卡,门禁系统看到熟悉的卡就直接放行,不需要重新检查。
useCallback就像是告诉React:“嘿,除非这些特定条件改变了,否则就重复使用之前的那个函数,不要创建新的。”
// ❌ 问题代码:每次Parent渲染,子组件也跟着渲染
function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// 问题所在:每次渲染都创建新函数
const handleButtonClick = () => {
console.log('Button clicked!');
};
return (
计数器: {count}
setText(e.target.value)}
placeholder="输入些什么..."
/>
{/* 即使text变化与Button无关,Button也会重新渲染 */}
);
}
// 使用React.memo包装的"昂贵"组件
const ExpensiveButton = React.memo(({ onClick }) => {
console.log('ExpensiveButton 渲染');
// 假设这是一个渲染成本高的组件
return ;
});
function ParentComponent() {
const [count, setCount] = useState(0);
const [text, setText] = useState('');
// 解决方案:使用useCallback记忆函数
const handleButtonClick = useCallback(() => {
console.log('Button clicked!');
}, []); // 空依赖数组 = 函数引用永不改变
return (
计数器: {count}
setText(e.target.value)}
placeholder="输入些什么..."
/>
{/* 现在当text变化时,Button不会重新渲染 */}
);
}
function ProductList({ category }) {
const [products, setProducts] = useState([]);
const [sortOrder, setSortOrder] = useState('asc');
// 依赖category和sortOrder的函数
const fetchProducts = useCallback(() => {
console.log(`Fetching ${category} products, sorted ${sortOrder}`);
fetch(`/api/products?category=${category}&sort=${sortOrder}`)
.then(res => res.json())
.then(data => setProducts(data));
}, [category, sortOrder]); // ✨ 只有当这些值变化时,才会创建新函数
// 首次加载和依赖变化时获取产品
useEffect(() => {
fetchProducts();
}, [fetchProducts]);
return (
{category} Products
{/* 传递记忆化的函数给子组件 */}
{products.map(product => (
- {product.name}
))}
);
}
// 使用React.memo优化
const RefreshButton = React.memo(({ onRefresh }) => {
console.log('RefreshButton rendered');
return ;
});
function SearchPanel() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
// 记忆化搜索函数
const handleSearch = useCallback((searchTerm) => {
console.log(`Searching for: ${searchTerm}`);
performSearch(searchTerm).then(setResults);
}, []); // 搜索逻辑不依赖于组件状态
return (
handleSearch(query)}
/>
{/* FilterPanel接收稳定的函数引用 */}
);
}
// 使用React.memo优化子组件
const FilterPanel = React.memo(({ onFilterChange }) => {
// 复杂的筛选UI...
return (
);
});
function ChatRoom({ roomId }) {
const [messages, setMessages] = useState([]);
// 使用useCallback记忆化函数
const handleNewMessage = useCallback((msg) => {
setMessages(prev => [...prev, msg]);
}, []); // 不依赖任何变量,引用稳定
useEffect(() => {
// 连接到聊天室
const connection = createConnection(roomId, handleNewMessage);
connection.connect();
return () => connection.disconnect();
}, [roomId, handleNewMessage]); // handleNewMessage是依赖项
return (
Room: {roomId}
);
}
function DataFetcher() {
// 创建一个稳定的fetcher函数引用
const fetchData = useCallback((endpoint) => {
return fetch(`/api/${endpoint}`).then(r => r.json());
}, []);
// 在组件多处使用相同函数
return (
);
}
React如何"记住"函数? 让我们简化理解useCallback的工作方式:
// 这是React内部对useCallback的简化实现
function useCallback(callback, dependencies) {
// 使用useMemo返回callback本身
return useMemo(() => callback, dependencies);
}
React维护一个"记忆单元",存储:
每次渲染时,React会比较当前依赖项与存储的依赖项,如果相同则返回缓存的函数,否则更新缓存并返回新函数。
// ✨ 构建高效UI的完整示例
function TodoApp() {
const [todos, setTodos] = useState([]);
const [newTodo, setNewTodo] = useState('');
const [filter, setFilter] = useState('all');
// 添加待办
const addTodo = useCallback(() => {
if (!newTodo.trim()) return;
const todo = {
id: Date.now(),
text: newTodo,
completed: false
};
setTodos(prev => [...prev, todo]);
setNewTodo('');
}, [newTodo]);
// 切换完成状态
const toggleTodo = useCallback((id) => {
setTodos(prev =>
prev.map(todo =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo
)
);
}, []);
// 删除待办
const deleteTodo = useCallback((id) => {
setTodos(prev => prev.filter(todo => todo.id !== id));
}, []);
// 过滤显示的待办项
const filteredTodos = useMemo(() => {
switch(filter) {
case 'active':
return todos.filter(todo => !todo.completed);
case 'completed':
return todos.filter(todo => todo.completed);
default:
return todos;
}
}, [todos, filter]);
return (
待办事项
{/* 输入框不会因为过滤条件变化而重新渲染 */}
{/* 过滤器不会因为添加任务而重新渲染 */}
{/* 列表只在filteredTodos变化时重新渲染 */}
);
}
// 使用React.memo优化子组件
const TodoInput = React.memo(({ value, onChange, onAdd }) => {
console.log('TodoInput rendered');
return (
onChange(e.target.value)}
placeholder="添加新任务..."
/>
);
});
const FilterButtons = React.memo(({ currentFilter, onChange }) => {
console.log('FilterButtons rendered');
return (
);
});
const TodoList = React.memo(({ todos, onToggle, onDelete }) => {
console.log('TodoList rendered');
return (
{todos.map(todo => (
))}
);
});
const TodoItem = React.memo(({ todo, onToggle, onDelete }) => {
console.log(`TodoItem ${todo.id} rendered`);
return (
onToggle(todo.id)}
/>
{todo.text}
);
});
// useCallback缓存函数引用
const handleClick = useCallback(() => {
console.log(count);
}, [count]);
// useMemo缓存计算结果
const doubledCount = useMemo(() => {
return count * 2;
}, [count]);
// 它们之间的关系
// 这两种写法是等价的:
const fn1 = useCallback(() => {
console.log('Hello');
}, []);
const fn2 = useMemo(() => {
return () => {
console.log('Hello');
};
}, []);
function SearchComponent({ initialQuery }) {
const [results, setResults] = useState([]);
// ❌ 错误:缺少依赖项
const search = useCallback(() => {
fetchResults(initialQuery).then(setResults);
}, []); // 依赖数组没有包含initialQuery
// ✅ 正确:包含所有依赖项
const searchCorrect = useCallback(() => {
fetchResults(initialQuery).then(setResults);
}, [initialQuery]); // 正确包含依赖项
// ❌ 错误:内联对象作为依赖
const searchOptions = { term: initialQuery, limit: 10 };
const searchWithOptions = useCallback(() => {
fetchWithOptions(searchOptions);
}, [searchOptions]); // searchOptions每次渲染都是新对象
// ✅ 正确:使用对象字段作为依赖
const searchWithOptionsFix = useCallback(() => {
fetchWithOptions({ term: initialQuery, limit: 10 });
}, [initialQuery]); // 直接依赖原始值
}
function App() {
// ✅ 对这种函数没必要使用useCallback
const simpleHandler = () => {
console.log('Clicked');
};
// ✅ 对这种函数使用useCallback是有价值的
const expensiveComponentHandler = useCallback(() => {
console.log('Handling expensive component interaction');
}, []);
return (
<>
{/* 普通组件不需要优化 */}
{/* 记忆化的昂贵组件需要稳定的props */}
>
);
}
// 父组件
function Parent() {
// 使用useCallback记忆化函数
const handleClick = useCallback(() => {
console.log('Clicked');
}, []);
return ;
}
// 子组件使用React.memo优化
const ChildButton = React.memo(({ onClick }) => {
console.log('ChildButton render');
return ;
});
function SearchComponent({ term }) {
const [results, setResults] = useState([]);
// 使用useRef存储最新值,但不作为依赖
const termRef = useRef(term);
// 更新ref值
useEffect(() => {
termRef.current = term;
}, [term]);
// 依赖稳定的函数
const search = useCallback(() => {
// 总是读取最新值
const currentTerm = termRef.current;
fetchResults(currentTerm).then(setResults);
}, []); // 空依赖数组,函数引用稳定
return (
);
}
function UserProfile({ userId, onUpdate }) {
// ❌ 过度依赖
const handleProfileUpdate = useCallback((data) => {
// 更新逻辑
console.log(`Updating user ${userId} with ${JSON.stringify(data)}`);
onUpdate(userId, data);
}, [userId, onUpdate]);
// ✅ 更好的方式:分离不变的逻辑
const handleFormSubmit = useCallback((data) => {
// 不依赖特定用户ID的逻辑
if (!data.name) return;
onUpdateProfile(data);
}, []);
// 将依赖移到直接使用它的地方
const onUpdateProfile = useCallback((data) => {
console.log(`Updating user ${userId} with ${JSON.stringify(data)}`);
onUpdate(userId, data);
}, [userId, onUpdate]);
return (
);
}
useCallback的核心是记忆化函数引用,通过这种方式:
useCallback就像是给函数颁发了一张"免重新创建"通行证,只有在真正需要时(依赖变化时)才会更新这张通行证。这大大减少了React城堡中不必要的"函数制造"工作,让你的应用跑得更快、更流畅!