前言
接續前一篇,這篇是 useMemo,請直接開始閱讀文章!
文章架構
如上篇,我會按照官方文件的架構,首先講解定義,再用一些簡單的範例講解應該怎麼使用這些 Hooks,最後再講解一些容易犯錯的用例。
定義
用來快取常常需要重新計算的狀態,如果有用過 vue.js 的 computed,應該很容易上手這個 hook。
const cachedValue = useMemo(calculateValue, dependencies)
我們可以看到他分成三個部分:
- 傳入
- calculateValue: 傳入一個 function,用來計算出每次的結果並回傳。
- dependencies: 用來關注那些變動了就應該重新計算狀態的變數。
- 傳出
- cachedValue: 最新的計算好的 state,和其他 hook 提供的一樣都是 read only。
範例
以下是使用 useMemo 的範例。
// without useMemo
// component need to recalculate todo list,
// even props are the same.
export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={filterTodos(todos, tab)} />
</div>
);
}
// with useMemo
// when props are the same,
// component will use cached todos from latest render.
import { useMemo } from 'react';
function TodoList({ todos, tab }) {
const visibleTodos = useMemo(
() => filterTodos(todos, tab),
[todos, tab]
);
// ...
}
export default function TodoList({ todos, tab, theme }) {
// ...
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}
由於父元件重新渲染時,也會同時將所有子元件重新渲染,如果想要避免大量重新渲染時,有兩種方式,用 useMemo 把元件先計算好之後存起來(block 1),或是用 memo 做個別元件的快取,這樣可以更簡單寫出想要的效果(block 2)。
// block 1
//
export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
const children = useMemo(() => <List items={visibleTodos} />, [visibleTodos]);
return (
<div className={theme}>
{children}
</div>
);
}
// block 2
import { memo } from 'react';
export default function TodoList({ todos, tab, theme }) {
const visibleTodos = useMemo(() => filterTodos(todos, tab), [todos, tab]);
return (
<div className={theme}>
<List items={visibleTodos} />
</div>
);
}
const List = memo(function List({ items }) {
// ...
});
注意事項
1. 不要在 useMemo 裡面更新 dependencies
因為 useMemo 會關注 dependencies,所以如果在 useMemo 裡面更新 dependencies 的話,就會循環觸發 useMemo
// wrong usage
const visibleTodos = useMemo(() => {
// 🚩 Mistake: mutating a prop
todos.push({ id: 'last', text: 'Go for a walk!' });
const filtered = filterTodos(todos, tab);
return filtered;
}, [todos, tab]);
// correct usage
const visibleTodos = useMemo(() => {
const filtered = filterTodos(todos, tab);
// ✅ Correct: mutating an object you created during the calculation
filtered.push({ id: 'last', text: 'Go for a walk!' });
return filtered;
}, [todos, tab]);
2. 在對的地方使用 useMemo
請不要在迴圈中使用 useMemo,如果要在這種場景下使用,請參考下面的修正方式。
// use useMemo in a loop
function ReportList({ items }) {
return (
<article>
{items.map(item => {
// 🔴 You can't call useMemo in a loop like this:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure key={item.id}>
<Chart data={data} />
</figure>
);
})}
</article>
);
}
// fix solution
function ReportList({ items }) {
return (
<article>
{items.map(item =>
<Report key={item.id} item={item} />
)}
</article>
);
}
function Report({ item }) {
// ✅ Call useMemo at the top level:
const data = useMemo(() => calculateReport(item), [item]);
return (
<figure>
<Chart data={data} />
</figure>
);
}
結語
這個 hook 挺簡單的,但是也是常常會用到的一種,需要好好記住使用場景。