React — UseCallback
6 min readMay 5, 2025
按照慣例,讓我們先看沒使用 UseCallback 導致的悲慘事件
TodoItem.tsx
import { memo } from "react";
interface TodoItemProps {
id: number;
text: string;
onDelete: (id: number) => void;
}
// 使用 memo 記憶化元件,只有當 props 改變時才會重新渲染
const TodoItem = memo(({ id, text, onDelete }: TodoItemProps) => {
console.log(`TodoItem ${id} 重新渲染了`);
return (
<div className="flex items-center justify-between p-4 bg-white rounded-lg shadow mb-2">
<span className="text-gray-700">{text}</span>
<button
onClick={() => onDelete(id)}
className="px-3 py-1 text-sm text-red-600 hover:bg-red-100 rounded"
>
刪除
</button>
</div>
);
});
TodoItem.displayName = "TodoItem";
export default TodoItem;Todo.tsx
import { useState } from "react";
import TodoItem from "./TodoItem";
interface Todo {
id: number;
text: string;
}
export default function Todo() {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState<Todo[]>([
{ id: 1, text: "學習 React" },
{ id: 2, text: "學習 TypeScript" },
{ id: 3, text: "學習 Tailwind CSS" },
]);
// 這個函數在每次組件重新渲染時都會創建新的實例
const handleDelete = (id: number) => {
setTodos(todos.filter((todo) => todo.id !== id));
};
return (
<div className="max-w-md mx-auto mt-8 p-4">
<div className="mb-8 text-center">
<p className="text-xl mb-4">計數器: {count}</p>
<button
onClick={() => setCount((c) => c + 1)}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
增加計數
</button>
</div>
<div className="space-y-4">
{todos.map((todo) => (
<TodoItem
key={todo.id}
id={todo.id}
text={todo.text}
onDelete={handleDelete}
/>
))}
</div>
</div>
);
}雖然增加計數的功能是跟 todo list 無關的,但每次 todo items 還是重新渲染
Get jimmy.dev’s stories in your inbox
Join Medium for free to get updates from this writer.
要修正這問題,就要用 useCallback 包住 handleDelete
使用 useCallback 的 Todo.tsx
import { useState, useCallback } from "react";
import TodoItem from "./TodoItem";
interface Todo {
id: number;
text: string;
}
export default function Todo() {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState<Todo[]>([
{ id: 1, text: "學習 React" },
{ id: 2, text: "學習 TypeScript" },
{ id: 3, text: "學習 Tailwind CSS" },
]);
// 使用 useCallback 記憶化函數,只有當 todos 改變時才會重新創建
const handleDelete = useCallback(
(id: number) => {
setTodos(todos.filter((todo) => todo.id !== id));
},
[todos]
);
return (
<div className="max-w-md mx-auto mt-8 p-4">
<div className="mb-8 text-center">
<p className="text-xl mb-4">計數器: {count}</p>
<button
onClick={() => setCount((c) => c + 1)}
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600"
>
增加計數
</button>
</div>
<div className="space-y-4">
{todos.map((todo) => (
<TodoItem
key={todo.id}
id={todo.id}
text={todo.text}
onDelete={handleDelete}
/>
))}
</div>
</div>
);
}使用時機
- 當函數作為 props 傳給 memo 組件時
- 當函數被用在其他 hooks 的依賴陣列中時
最佳實踐 — 上述例子還能再這樣優化
// 好的寫法:使用函數式更新,不需要依賴外部變數
const handleDelete = useCallback((id: number) => {
setTodos((prevTodos) => prevTodos.filter((todo) => todo.id !== id));
}, []); // 空依賴陣列
// 避免的寫法:直接依賴外部變數
const handleDelete = useCallback((id: number) => {
setTodos(todos.filter((todo) => todo.id !== id));
}, [todos]); // 需要依賴 todos






