Hannah Lin 总结
该网页是一篇关于 React Hooks 的深入学习文章,专注于介绍 Memorized Hooks,包括 useMemo 和 useCallback 的使用和区别,以及如何在 TypeScript 中对这些 Hooks 进行类型标注。
摘要
文章首先介绍了 React Hooks 的基础知识,并指出了网络上大多数文章只介绍 useState 和 useEffect 的现象。作者提供了一个系列文章的索引,包括对 useState、useEffect、useMemo、useCallback、useRef、useContext、useReducer 和 useLayoutEffect 的深入探讨。文章强调了 useMemo 和 useCallback 的重要性,它们可以帮助开发者避免在每次渲染时执行昂贵的计算,从而提高应用性能。
文章详细解释了 useMemo 和 useCallback 的工作原理,并通过示例代码展示了它们的应用场景。useMemo 用于存储计算结果,而 useCallback 用于存储回调函数,两者都依赖于依赖数组来决定何时重新计算或重新创建回调函数。文章还指出了这些 Hooks 的不同之处,例如 useCallback 返回的是一个记忆化的回调函数,而 useMemo 返回的是记忆化的值。
此外,文章还讨论了在不适当的情况下使用 Memorized Hooks 可能导致的性能问题,并提供了一些最佳实践,如何在 TypeScript 中正确地为这些 Hooks 添加类型标注。最后,作者提供了一些参考资源,包括官方文档和相关视频教程,供读者进一步学习。
观点
useMemo 和 useCallback 的主要作用是优化性能 ,通过避免在每次渲染时重新执行昂贵的计算和创建函数实例。
适用场景 :当函数执行速度慢且结果不需要频繁变动,且是纯函数时,使用 useMemo 和 useCallback 特别有效。
记忆化的依赖性 :这两个 Hooks 都依赖于依赖数组,只有当依赖项发生变化时,才会重新计算值或创建新的函数实例。
不同之处 :useCallback 返回一个记忆化的回调函数,useMemo 返回一个记忆化的值。
类型标注 :在 TypeScript 中,正确地为 useCallback 和 useMemo 添加类型标注,可以提高代码的可读性和可维护性。
性能考虑 :不建议盲目地对所有函数使用记忆化 Hooks,因为不当使用可能会导致额外的内存开销和性能问题。
最佳实践 :只有在确实需要优化性能的情况下才应使用 useMemo 和 useCallback,并且应该尽量减少依赖数组中的元素,以避免不必要的重渲染。
[React Hook 筆記] Memorized Hook- useMemo, useCallback 把東西用 useMemo/useCallback 存起來就不用每次重新 render 拖慢效能 (新增 typing 於 2023/11/7)
React Hook 系列文
1. 從最基本的 Hook 開始 useState, useEffect
2. Memorized Hook- useMemo, useCallback
3. useRef
4. useContext
5. useReducer
6. useLayoutEffect
7. Custom Hooks
圖改編自 kevinwkds.medium.com 至從 React Hook 興起,網路上多數文章都只介紹 useState 跟 useEffect,但明明 Hook 還有很多別的,所以這篇想先從比較少看到的 Memorized Hook 開始。若 useState 跟 useEffect 都很不熟可以先閱讀 從最基本的 Hook 開始 useState, useEffect 再回來看此篇
1. Memorized Hook 在幹嘛?
2. useMemo
3. useCallback
4. useCallback 跟 useMemo 有什麼不同
5. Typing useCallback/useMemo
6. 結論Memorized Hook 在幹嘛? 顧名思義就是把東西存起來就不用每次都重新 render, 最常使用的情境就是當 function 符合以下三點
執行速度很慢 (Expensive function or expensive calculation)不需要常常再被 render over and over again ,變動性不大Pure function (每次被呼叫 output 都會一樣)
function slowFunc (num ) {
for (let i=0 ; i<=1000000 ; i++) {} const double Num = slowFunc(1 ); 若執行 slowFunction 需要 10 秒鐘,那執行三次就需要 30 秒; 但若先存起來,那只有第一次需要 10 秒,之後若需要他,直接從名為 doubleNum抽屜拿已經存起來的值 2 就好了
不過只要這函式不是 pure function 或是會產生 side effect 就不適合用 memoize
const getCurrentTimeMemoized = memoize (Date .now );
getCurrentTimeMemoized ();
getCurrentTimeMemoized ();
getCurrentTimeMemoized ();
function uploadRow (row ) {
}
const memoizedUpload = memoize (uploadRows);
memoizedUpload ('Some Data' );
memoizedUpload ('Some Data' ); 另外很常搞混 Memorized Hook 的 useMemo 跟 useCallback,這邊先講結論下面會再詳細比較
useCallback(fn, deps) 等同於 useMemo(() => fn, deps)
Note. 其實在 Hook 出現前已經有一些方法來 memorized
createSelector (Create a selector for redux state and memorizes the result)React.memo (Memoizes a React functional component based on its props)useMemo Returns a memoized value. 也就是 dependencies 沒有改變的情況下,把某個運算的值 保存下來 ( 這個 slowFunction 回傳值可以是 object、array、basic type)
const memoizedValue = useMemo(() => slowFunction(a, b), [a, b]); https://codepen.io/hannahpun/pen/ExgRbLo?editors=1011 用法 1: 暫存起來 把執行速度慢且不需要常常再被 call 的函式結果存起來。直接來看範例比較容易,以下是一個非常簡單的 React Component
setNumber 會觸發 re-render App 這個 component,所以可以預想每一次 slowFunction 都會被重新呼叫並執行
這其實是非常影響效能的一件事,因為 slowFunction 不但執行速度慢 而且明明每次回傳值都ㄧ樣 但卻要一直重覆被執行。為了提高效能,可以把他的值先存起來
const doubleNumber = useMemo(() => {
return slowFunction(number )
}, [number ]) useMemo 第一個參數是放函式,第二個參數是該 Memo 所依賴的值 array,意思跟 useEffect 第二個依賴 Array 參數一樣,有變動的話才會重新 render 此函式
以上可以看到 doubleNumber 是依賴 number,所以只有當 number 有變動時才會重新執行 slowFunction ,當 setDark時並不會 re-render doubleNumber,也就是不會 re-call slowFunction因為我們知道他的值就是 last time 回傳的值; 反之 themeStyle 裡函示也是一樣概念
猜猜若沒有包進 useMemo 那 console 出來會是什麼呢?
"Calling Slow Function"
"Theme Change"
"Calling Slow Function"
"Theme Change"
"Calling Slow Function"
"Theme Change" 用法 2: 解決 {} != {} 大家知道 javaScript 的函式也是物件的一種,然後物件是 by reference 所以
const a = {name: 'Hannah' };
const b = {name: 'Hannah' };有了這個觀念再來看以下例子
const App = () => {
const [dark, setDark] = useState(true ); const themeStyle = {
backgroundColor : dark ? '#2c3e50' : '#ecf0f1' ,
color : dark ? '#ecf0f1' : '#2c3e50'
}
useEffect(() => {
console .log ('Theme Change' )
}, [themeStyle])
} 你會以為只要 themeStyle 沒變動,就不會一直印出 Theme Change ?
其實 js 會認為每一次 themeStyle 回傳的 Object 都是不相等的,所以才會不斷重新 render。 這時候就可以把 Object 包在 memo 裡,這樣 js 就會認得它是同一個 reference,就不會重新 re-render
const themeStyle = useMemo(() => {
return {
backgroundColor : dark ? '#2c3e50' : '#ecf0f1' ,
color : dark ? '#ecf0f1' : '#2c3e50'
}
}, [dark])
useEffect(() => {
console .log ('Theme Change' )
}, [themeStyle])
} 要謹記傳到 useMemo 的 function 會在 render 期間執行。不要做一些通常不會在 render 期間做的事情。例如,處理 side effect 屬於 useEffect,而不是 useMemo。
如果沒有提供 array,每次 render 時都會計算新的值。
處理 side effect 屬於 useEffect,而不是 useMemo。
useCallBack Returns a memoized callback. 也就是 dependencies 沒有改變的情況下,把某個 function 保存下來
const memoizedCallback = useCallback(
() => {
doSomething(a, b);
},
[a, b],
); https://codepen.io/hannahpun/pen/yLaRyvM
會發現其實 useCallback 跟 useMemo 用法真的大同小異
useCallback 跟 useMemo 有什麼不同 useCallback 回傳 callBack function,所以可以傳參數進去useMemo 回傳值useCallback(fn, deps) 等同於 useMemo(() => fn, deps)
Typing useCallback/useMemo 加 type 進 useCallback/useMemo 沒什麼困難度,只要記得
useCallback 回傳 function,useMemo 回傳值
const doubleNumber = useMemo<number >(() => {
return slowFunction (number )
}, [number ])
const doubleNumber = useCallback ((arg: string ) => {
return slowFunction (number )
}, [number ])
const doubleNumber = useCallback<number >(() => {
return slowFunction (number )
}, [number ]) 結論 既然 Memorized Hook 可以節省效能那乾脆所有函式都包進去不就完美?!
其實不建議全部都用 Memorized Hook,因為可能會有 memory overhead, memory usage 太多也是會有 performance issue 的,例如若頻繁需要 render 的就不建議使用,所以記得適用的函式要符合
執行速度很慢 不需要常常再被 render over and over again ,變動性不大Reference 不得不說 React 官網 已經寫得相當好,這邊也參考以下
Recommended from ReadMedium