【React】useMemoをなんとなくで使っていたので、ざっくりまとめてみました
useMemoとは
概要
- ReactHooksのひとつ
- 関数の計算結果を保持するためのフック
- 何回行っても結果が同じ値を保存して再取得します
- パフォーマンス向上のために使用されます
const cachedValue = useMemo(calculateValue, dependencies)
引数
-
calculateValue
:キャッシュしたい値を計算する関数。純関数[1]である必要があります。 -
dependencies
:calculateValue
内で参照されるすべてのリアクティブ値の配列。これが変わると再計算されます。
const result = useMemo(() => num * 2, [num]);
この場合 num
が更新された際に、再計算される
返り値
初回レンダー時:calculateValue
の結果を返します。
次回以降のレンダー時:依存配列が変わらなければキャッシュされた値を返し、変われば再計算してその結果を返します。
注意点
- フックなので、コンポーネントのトップレベル[2]でのみ使用可能。
- ループや条件分岐の中では使用不可。
- 特別な理由がない限り、キャッシュは破棄されませんが、将来的に変更される可能性があります。
使用場面
①高コストな再計算を避ける
useMemo
は、高コストな再計算を避けるために、計算結果をキャッシュします。
例
通常、Reactでは再レンダーが発生するたびにコンポーネント関数全体が再実行されます。以下の例では、filterTodos
関数が毎回再実行されます。
const TodoList = ({ todos, tab, theme }) => {
const visibleTodos = filterTodos(todos, tab);
// ...
};
しかし、useMemo
を使用することで、todos
と tab
が変わらない限り、以前の計算結果を再利用できます。
const TodoList = ({ todos, tab, theme }) => {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
// ...
};
②コンポーネントの再レンダリングをスキップ
useMemo
は、子コンポーネントの再レンダリングのパフォーマンスを最適化する際にも役に立つことがあります
例①
以下の例では、TodoList
コンポーネントが List
コンポーネントに visibleTodos
を渡しています。
const TodoList = ({ todos, tab, theme }) => {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
};
theme
が変わると、List
コンポーネントも再レンダリングされ、アプリが一瞬フリーズすることがあります。これは、親コンポーネントが再レンダリングされると、子コンポーネントも再帰的に再レンダリングされるためです。
解決策として、List
コンポーネントを memo
でラップし、props
が変わらない限り再レンダリングをスキップします。
const List = memo(({ items }) => {
// ...
});
例②
const TodoList = ({ todos, tab, theme }) => {
const visibleTodos = filterTodos(todos, tab);
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
};
useMemo
を使わずに visibleTodos
を計算すると、filterTodos
が毎回異なる配列を生成し、List
の再レンダリングをスキップできません。
そこで、useMemo
を使うことで、依存配列(todos
と tab
)が変わらない限り、計算結果をキャッシュし、同じ visibleTodos
を渡すことができます。
const TodoList = ({ todos, tab, theme }) => {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
};
③他フックに渡す依存値をメモ化
他のフックに渡す依存値をメモ化する方法
例
コンポーネント内で直接作成されたオブジェクトを依存値として使用すると、再レンダー時に毎回新しいオブジェクトが生成され、メモ化の効果が失われます。
以下の例では、searchOptions
オブジェクトが毎回新しく生成されるため、useMemo
の依存値が毎回変わり、再計算が発生します。
const Dropdown = ({ allItems, text }) => {
const searchOptions = { matchMode: 'whole-word', text };
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // 🚩 注意: コンポーネント本体で作成されたオブジェクトに依存
// ...
};
解決策①: searchOptions をメモ化
searchOptions
オブジェクト自体を useMemo
でメモ化します。
const Dropdown = ({ allItems, text }) => {
const searchOptions = useMemo(() => {
return { matchMode: 'whole-word', text };
}, [text]); // ✅ text が変わらない限り変化しない
const visibleItems = useMemo(() => {
return searchItems(allItems, searchOptions);
}, [allItems, searchOptions]); // ✅ allItems または searchOptions が変わったときのみ再計算
// ...
};
解決策②: useMemo の計算関数内にオブジェクトを移動
さらに良い方法は、オブジェクトの宣言を useMemo
の計算関数内に移動することです。
const Dropdown = ({ allItems, text }) => {
const visibleItems = useMemo(() => {
const searchOptions = { matchMode: 'whole-word', text };
return searchItems(allItems, searchOptions);
}, [allItems, text]); // ✅ allItems または text が変わったときのみ再計算
// ...
};
④関数のメモ化
関数をメモ化する方法
例
memo
でラップされたコンポーネントに関数を props
として渡す場合、毎回新しい関数が生成されるとメモ化の効果が失われます。
以下の例では、handleSubmit
関数が毎回新しく生成されるため、Form
コンポーネントの再レンダーがスキップされません。
const ProductPage = ({ productId, referrer }) => {
const handleSubmit = (orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
};
return <Form onSubmit={handleSubmit} />;
};
関数を useMemo
でメモ化します。
const ProductPage = ({ productId, referrer }) => {
const handleSubmit = useMemo(() => {
return (orderDetails) => {
post('/product/' + productId + '/buy', {
referrer,
orderDetails
});
};
}, [productId, referrer]);
return <Form onSubmit={handleSubmit} />;
};
参考資料
▼React公式
Discussion