React useCallback
Hook
React 的 useCallback
Hook 返回一個記憶化的回撥函式。
將記憶化看作是快取一個值,這樣它就不需要被重新計算了。
這使我們可以隔離資源密集型函式,使它們不會在每次渲染時自動執行。
useCallback
Hook 僅在其依賴項更新時執行。
這可以提高效能。
useCallback
和 useMemo
Hooks 類似。主要區別在於 useMemo
返回一個記憶化的*值*,而 useCallback
返回一個記憶化的*函式*。您可以在 useMemo 章節中瞭解更多關於 useMemo 的資訊。
問題
使用 useCallback
的一個原因是防止元件在 props 未更改時重新渲染。
在此示例中,您可能認為 Todos
元件在 todos
未更改時不會重新渲染
這與 React.memo 部分的示例類似。
示例
index.js
import { useState } from "react";
import ReactDOM from "react-dom/client";
import Todos from "./Todos";
const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const increment = () => {
setCount((c) => c + 1);
};
const addTodo = () => {
setTodos((t) => [...t, "New Todo"]);
};
return (
<>
<Todos todos={todos} addTodo={addTodo} />
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
</div>
</>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Todos.js
import { memo } from "react";
const Todos = ({ todos, addTodo }) => {
console.log("child render");
return (
<>
<h2>My Todos</h2>
{todos.map((todo, index) => {
return <p key={index}>{todo}</p>;
})}
<button onClick={addTodo}>Add Todo</button>
</>
);
};
export default memo(Todos);
嘗試執行此程式碼並點選計數器遞增按鈕。
您會注意到,即使 todos
未改變,Todos
元件也會重新渲染。
為什麼這不起作用?我們正在使用 memo
,所以 Todos
元件不應該重新渲染,因為在遞增計數時 todos
狀態和 addTodo
函式都沒有改變。
這是因為一個叫做“引用相等性”的東西。
每次元件重新渲染時,其函式都會被重新建立。因此,addTodo
函式實際上已經改變了。
解決方案
為了解決這個問題,我們可以使用 useCallback
hook 來防止函式在沒有必要時被重新建立。
使用 useCallback
Hook 來防止 Todos
元件不必要地重新渲染
示例
index.js
import { useState, useCallback } from "react";
import ReactDOM from "react-dom/client";
import Todos from "./Todos";
const App = () => {
const [count, setCount] = useState(0);
const [todos, setTodos] = useState([]);
const increment = () => {
setCount((c) => c + 1);
};
const addTodo = useCallback(() => {
setTodos((t) => [...t, "New Todo"]);
}, [todos]);
return (
<>
<Todos todos={todos} addTodo={addTodo} />
<hr />
<div>
Count: {count}
<button onClick={increment}>+</button>
</div>
</>
);
};
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(<App />);
Todos.js
import { memo } from "react";
const Todos = ({ todos, addTodo }) => {
console.log("child render");
return (
<>
<h2>My Todos</h2>
{todos.map((todo, index) => {
return <p key={index}>{todo}</p>;
})}
<button onClick={addTodo}>Add Todo</button>
</>
);
};
export default memo(Todos);
現在,Todos
元件僅在 todos
prop 更改時重新渲染。