5月28日 02:51
What is the difference between useCallback and useMemo? When to use them?
Background
useCallback and useMemo are two performance optimization hooks provided by React. They look similar but have fundamental differences in purpose and return values.
Core Difference
Syntax Comparison
jsx// useCallback: Returns the function itself const memoizedCallback = useCallback(() => { doSomething(a, b); }, [a, b]); // useMemo: Returns the result of function execution const memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Essential Differences
| Feature | useCallback | useMemo |
|---|---|---|
| Return Value | Function reference | Any value (computed result) |
| Purpose | Cache function | Cache computed result |
| Similar to | Function memoization | Value memoization |
| Use Case | Avoid function recreation | Avoid repeated computation |
useCallback Details
Basic Usage
jsxconst memoizedCallback = useCallback( () => { doSomething(a, b); }, [a, b] );
Use Cases
1. Passing to Child Components to Avoid Unnecessary Renders
jsxfunction Parent({ items }) { // ❌ Creates new function every render, causes Child to re-render const handleClick = () => { console.log("clicked"); }; // ✅ Use useCallback to cache function const handleClick = useCallback(() => { console.log("clicked"); }, []); return <Child onClick={handleClick} items={items} />; } // Use with React.memo const Child = React.memo(({ onClick, items }) => { console.log("Child render"); return <button onClick={onClick}>Click</button>; });
2. As useEffect Dependency
jsxfunction UserProfile({ userId }) { // ❌ fetchUser is new reference every time, useEffect runs every time const fetchUser = () => { api.getUser(userId); }; // ✅ Use useCallback to stabilize function reference const fetchUser = useCallback(() => { api.getUser(userId); }, [userId]); useEffect(() => { fetchUser(); }, [fetchUser]); }
useMemo Details
Basic Usage
jsxconst memoizedValue = useMemo(() => computeExpensiveValue(a, b), [a, b]);
Use Cases
1. Avoid Repeated Computation
jsxfunction ProductList({ products, filter }) { // ❌ Re-filters every render const filteredProducts = products.filter(p => p.name.includes(filter) ); // ✅ Only recomputes when products or filter changes const filteredProducts = useMemo(() => products.filter(p => p.name.includes(filter)), [products, filter] ); return <ul>{filteredProducts.map(p => <li key={p.id}>{p.name}</li>)}</ul>; }
2. Complex Computation Optimization
jsxfunction DataTable({ data }) { // ✅ Large data sorting computed once const sortedData = useMemo(() => { console.log("Sorting..."); return [...data].sort((a, b) => a.score - b.score); }, [data]); // ✅ Complex data transformation const chartData = useMemo(() => { return data.reduce((acc, item) => { // Complex aggregation logic return acc; }, {}); }, [data]); return <Chart data={chartData} />; }
3. Reference Stability
jsxfunction Parent({ items }) { // ❌ Creates new object every render, causes child re-render const style = { color: "red" }; // ✅ Keep object reference stable const style = useMemo(() => ({ color: "red" }), []); return <Child style={style} />; }
Best Practices
- Measure first, optimize later: Use React DevTools Profiler to find bottlenecks
- Simple computations do not need caching: Caching has overhead
- Use with React.memo: Ensure child components implement shouldComponentUpdate
- Declare dependencies correctly: Follow ESLint hints