您好,登錄后才能下訂單哦!
本篇內容介紹了“React18中useMemo、useCallback和memo怎么使用”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!
useMemo
是一個用于優化性能的 React 鉤子。它可以幫助我們避免在組件重新渲染時執行昂貴的計算。useMemo
接受兩個參數:一個函數和一個依賴數組。當依賴數組中的值發生變化時,useMemo
會重新計算并返回新的值。否則,它將返回上一次計算的值。
一個簡單的例子:
import React, { useMemo } from "react"; function ExpensiveComponent({ a, b }) { const result = useMemo(() => { console.log("Expensive calculation..."); return a * b; }, [a, b]); return <div>Result: {result}</div>; }
我們創建了一個名為 ExpensiveComponent
的組件,它接受兩個屬性 a
和 b
并使用 useMemo
鉤子來計算 a
和 b
的乘積。當 a
或 b
發生變化時,useMemo
會重新計算結果。否則,它將返回上一次計算的值,避免了不必要的計算。
useCallback
是另一個用于優化性能的 React 鉤子。它可以幫助我們避免在組件重新渲染時創建新的函數實例。useCallback
接受兩個參數:一個函數和一個依賴數組。當依賴數組中的值發生變化時,useCallback
會返回一個新的函數實例。否則,它將返回上一次創建的函數實例。
再看一個簡單的例子:
import React, { useCallback } from "react"; function ButtonComponent({ onClick, children }) { return <button onClick={onClick}>{children}</button>; } function ParentComponent() { const handleClick = useCallback(() => { console.log("Button clicked"); }, []); return ( <div> <ButtonComponent onClick={handleClick}>Click me</ButtonComponent> </div> ); }
在這個例子中,我們創建了一個名為 ButtonComponent
的組件,它接受一個 onClick
函數屬性。我們還創建了一個名為 ParentComponent
的組件,它使用 useCallback
鉤子來創建一個 handleClick
函數。當 ParentComponent
重新渲染時,useCallback
會返回上一次創建的 handleClick
函數實例,避免了不必要的函數創建。
memo
是一個用于優化性能的 React 高階組件。它可以幫助我們避免在父組件重新渲染時重新渲染子組件。memo
接受一個組件作為參數,并返回一個新的組件。當新組件的屬性發生變化時,它會重新渲染。否則,它將跳過渲染并返回上一次渲染的結果。
繼續舉例子:
import React, { memo } from "react"; const ChildComponent = memo(function ChildComponent({ text }) { console.log("ChildComponent rendered"); return <div>{text}</div>; }); function ParentComponent({ showChild }) { return ( <div> {showChild && <ChildComponent text="Hello, world!" />} <button onClick={() => setShowChild(!showChild)}>Toggle child</button> </div> ); }
在這個例子中,我們創建了一個名為 ChildComponent
的組件,并使用 memo
高階組件對其進行了優化。我們還創建了一個名為 ParentComponent
的組件,它可以切換 ChildComponent
的顯示。當 ParentComponent
重新渲染時,ChildComponent
的屬性沒有發生變化,因此它不會重新渲染。
用法都很清楚了,接下來總結一下它們之間的區別:
useMemo
用于避免在組件重新渲染時執行昂貴的計算,只有在依賴發生變化時重新計算值。
useCallback
用于避免在組件重新渲染時創建新的函數實例,只有在依賴發生變化時返回新的函數實例。
memo
用于避免在父組件重新渲染時重新渲染子組件,只有在屬性發生變化時重新渲染組件。
雖然這些功能都可以幫助我們優化性能,但它們的使用場景和工作原理有所不同。在實際開發中,需要因地制宜合理選用。
為了更深入地了解 useMemo
、useCallback
和 memo
的工作原理,我們將繼續分析 React 18
的源碼。我們將關注這些功能的核心邏輯,并詳細解釋它們的功能。
眾所周知,在React hooks
的體系中,每個鉤子都有自己各個階段的執行邏輯,并且存到對應的Dispatcher
中。
就拿useMemo來舉例:
// 掛載時的調度器 const HooksDispatcherOnMount: Dispatcher = { // useMemo 掛載時的執行函數 useMemo: mountMemo, // other hooks... }; // 數據更新時的調度器 const HooksDispatcherOnUpdate: Dispatcher = { // useMemo 掛載時的執行函數 useMemo: updateMemo, // other hooks... }; // 其他生命周期調度器...
上面代碼可以看出,useMemo
在掛載時執行了的是 mountMemo
, 而在更新數據時執行的是 updateMemo
。但為了更好了解 useMemo
、useCallback
和 memo
的區別,我們只看更新部分就足夠了。
源碼在packages/react-reconciler/src/ReactFiberHooks.js
中可以找到:
function updateMemo<T>( nextCreate: () => T, deps: Array<mixed> | void | null, ): T { const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; const prevState = hook.memoizedState; // Assume these are defined. If they're not, areHookInputsEqual will warn. if (nextDeps !== null) { const prevDeps: Array<mixed> | null = prevState[1]; if (areHookInputsEqual(nextDeps, prevDeps)) { return prevState[0]; } } if (shouldDoubleInvokeUserFnsInHooksDEV) { nextCreate(); } const nextValue = nextCreate(); hook.memoizedState = [nextValue, nextDeps]; return nextValue; }
在 updateMemo
的實現中,有一個關鍵函數 areHookInputsEqual
,它用于比較依賴項數組:
function areHookInputsEqual( nextDeps: Array<mixed>, prevDeps: Array<mixed> | null, ): boolean { if (__DEV__) { if (ignorePreviousDependencies) { // Only true when this component is being hot reloaded. return false; } } if (prevDeps === null) { if (__DEV__) { console.error( '%s received a final argument during this render, but not during ' + 'the previous render. Even though the final argument is optional, ' + 'its type cannot change between renders.', currentHookNameInDev, ); } return false; } if (__DEV__) { // Don't bother comparing lengths in prod because these arrays should be // passed inline. if (nextDeps.length !== prevDeps.length) { console.error( 'The final argument passed to %s changed size between renders. The ' + 'order and size of this array must remain constant.\n\n' + 'Previous: %s\n' + 'Incoming: %s', currentHookNameInDev, `[${prevDeps.join(', ')}]`, `[${nextDeps.join(', ')}]`, ); } } // $FlowFixMe[incompatible-use] found when upgrading Flow for (let i = 0; i < prevDeps.length && i < nextDeps.length; i++) { // $FlowFixMe[incompatible-use] found when upgrading Flow if (is(nextDeps[i], prevDeps[i])) { continue; } return false; } return true; }
areHookInputsEqual
函數接受兩個依賴項數組 nextDeps
和 prevDeps
。它首先檢查兩個數組的長度是否相等,如果不相等,將在開發模式下發出警告。然后,它遍歷數組并使用 is
函數(類似于 Object.is
)逐個比較元素。如果發現任何不相等的元素,函數將返回 false
。否則,返回 true
。
這個函數在 useMemo
的實現中起到了關鍵作用,因為它決定了是否需要重新計算值。如果依賴項數組相等,useMemo
將返回上一次計算的值;否則,它將執行 nextCreate
函數并返回一個新的值。
由于 useCallback
和 useMemo
實現一致,其原理都是通過areHookInputsEqual
函數進行依賴項比對,區別在于 useMemo
返回是新數據對象,而 useCallback
返回是回調函數。源碼如下:
function updateCallback<T>(callback: T, deps: Array<mixed> | void | null): T { const hook = updateWorkInProgressHook(); const nextDeps = deps === undefined ? null : deps; const prevState = hook.memoizedState; if (nextDeps !== null) { const prevDeps: Array<mixed> | null = prevState[1]; if (areHookInputsEqual(nextDeps, prevDeps)) { return prevState[0]; } } hook.memoizedState = [callback, nextDeps]; return callback; }
在 memo
的實現中,有一個關鍵函數 updateMemoComponent
,它用于更新 memo
組件。這個函數位于 packages/react-reconciler/src/ReactFiberBeginWork.js
文件中:
function updateMemoComponent( current: Fiber | null, workInProgress: Fiber, Component: any, nextProps: any, updateLanes: Lanes, renderLanes: Lanes, ): null | Fiber { if (current !== null) { // ... const prevProps = current.memoizedProps; const compare = Component.compare; const compareFn = compare !== null ? compare : shallowEqual; if (compareFn(prevProps, nextProps)) { return bailoutOnAlreadyFinishedWork( current, workInProgress, renderLanes, ); } } // ...render the component and return the result }
updateMemoComponent
函數首先檢查當前組件是否具有上一次的屬性 prevProps
。如果存在,它將獲取 memo
組件的比較函數 compare
。如果沒有提供比較函數,React 將使用默認的淺比較函數 shallowEqual
。
接下來,React 使用比較函數來檢查上一次的屬性 prevProps
是否與新的屬性 nextProps
相等。如果相等,React 將調用 bailoutOnAlreadyFinishedWork
函數來阻止組件重新渲染。否則,它將繼續渲染組件并返回結果。
bailoutOnAlreadyFinishedWork
函數的實現位于同一個文件中,它的核心邏輯如下:
function bailoutOnAlreadyFinishedWork( current: Fiber | null, workInProgress: Fiber, renderLanes: Lanes, ): null | Fiber { if (current !== null) { // Reuse previous dependencies workInProgress.dependencies = current.dependencies; } // ...some code // Check if the children have any pending work if ((workInProgress.childLanes & renderLanes) !== NoLanes) { // ...some code } else { // The children don't have any work. Set the bailout state. workInProgress.lanes = NoLanes; workInProgress.childLanes = NoLanes; return null; } // ...some code }
bailoutOnAlreadyFinishedWork
函數首先復用上一次的依賴項。然后,它檢查子組件是否有任何待處理的工作。如果沒有,它將設置 workInProgress.lanes
和 workInProgress.childLanes
為 NoLanes
,并返回 null
,從而阻止組件重新渲染。
“React18中useMemo、useCallback和memo怎么使用”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。