ReactuseCallback
フック
React useCallback
Hookは、メモ化されたコールバック関数を返します。
メモ化は、値をキャッシュして、再計算する必要がないようにすることと考えてください。
これにより、リソースを大量に消費する関数を分離して、すべてのレンダリングで自動的に実行されないようにすることができます。
useCallback
フックは、依存関係の1つが更新されたときにのみ実行されます。
これにより、パフォーマンスを向上させることができます。
とフックは似ていますuseCallback
。useMemo
主な違いはuseMemo
、メモ化された値をuseCallback
返し、メモ化された関数を返すことです。useMemoの詳細については、useMemoの章を参照してください。
問題
使用する理由の1つuseCallback
は、小道具が変更されていない限り、コンポーネントが再レンダリングされないようにすることです。
この例では、次の変更Todos
を行わない限り、コンポーネントは再レンダリングされないと考えるかもしれません。todos
これは、React.memoセクションの例と似ています。
例:
index.js
import { useState } from "react";
import ReactDOM from "react-dom";
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>
</>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
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
フックを使用して、必要な場合を除いて関数が再作成されないようにします。
useCallback
フックを使用して、Todos
コンポーネントが不必要に再レンダリングされないようにします。
例:
index.js
import { useState, useCallback } from "react";
import ReactDOM from "react-dom";
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>
</>
);
};
ReactDOM.render(<App />, document.getElementById('root'));
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