網頁中難免會有排序/過濾/資料整理等等的操作,有些列表式的查詢更可以一次列出一千多筆,只想排一下順序網頁就開始跟你鬧「彆扭」,這次來看看 useMemo
可以如何幫我們解決吧!
還記得當 state
改變時,component 也會跟著 re-render,其所有的內容都會跟著被重新建立,若裡面包含了大量運算,每次 render 就勢必要跟著再運算一次 (雖然結果一樣),因此 useMemo
可以幫我們做 Memoization ,減少不必要的體力(?)。
const memoValue = useMemo(() => someExpensiveCalculation(arg), [])
useMemo
會接受一個回傳的值,第一次執行後,只有在[ ] (dependencies)
內的內容被改變時,才會重新執行,除此之外, useMemo
回傳的值永遠會是相同的
假設我們有一組電影清單,想要按照年份(year)或是電影名稱(title)來切換排序
你可能會想這樣做:
useState
管理sortedData
依照狀態來排序資料const movies = [...]
function MovieList () {
const [sort, setSort] = useState('year')
const sortedData = [...movies].sort((a,b) => {
if(sort === 'year') {
return a.year - b.year
}
return a.title > b.title ? 1 : -1
})
return ...
}
這樣的嘗試是可以的,但延伸了一個問題:
每當 sort
改變 (或其他的state),元件會進行re-run,而 sortedData
就會重新執行一次 (指sort)
因此我們可以使用 useMemo
將兩種排序方式先計算一次,並在後續的 re-render 中仍然使用同樣的 value (此處可以透過DEMO來查看 console.log 的輸出)
const dataSortByYear = React.useMemo(() => {
console.log("sort by year");
return [...data].sort((a, b) => a.year - b.year);
}, []);
const dataSortByTitle = React.useMemo(() => {
console.log("sort by title");
return [...data].sort((a, b) => (a.title > b.title ? 1 : -1));
}, []);
const sortedData = sort === "year" ? dataSortByYear : dataSortByTitle;
「那這樣是不是把所有東西都套上 useMemo
就好惹?」
No! 與前一篇的 useCallback
相同,這樣的動作並不是免費的(換言之都有成本),因此你可以在幾個情況下使用:
而概念上:
useMemo
來記憶開銷很大的才能得到 value。 (但第一次還是得運算一次)useCallback
來記憶 function 於不同元件之間的傳遞或是 render 仍能維持一樣。前一篇有提到,當想使用 useCallback
時,可以思考看看是不會換個位置放就能解決問題 (換個位置:放到 React Component 的外面/useEffect
裡面),而 useMemo
使用頻率相比 useCallback
就會高一些。
分享當初寫這兩篇時參考的其中文章: Kent C. Dodds - When to useMemo and useCallback,