深入React?18源碼useMemo?useCallback?memo用法及區(qū)別分析
開篇
哈嘍大咖好,我是跑手,最近在做 React 相關的組件搭建,因為涉及到大量的圖形計算以及頁面渲染,所以特意翻了下性能優(yōu)化相關的hooks使用,如 useMemo、useCallback 和 memo。在這篇文章中,我們將探討這些功能的用法和區(qū)別,并通過源碼分析來理解它們的工作原理,開整!
用法
useMemo
useMemo 是一個用于優(yōu)化性能的 React 鉤子。它可以幫助我們避免在組件重新渲染時執(zhí)行昂貴的計算。useMemo 接受兩個參數:一個函數和一個依賴數組。當依賴數組中的值發(fā)生變化時,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>;
}
我們創(chuàng)建了一個名為 ExpensiveComponent 的組件,它接受兩個屬性 a 和 b 并使用 useMemo 鉤子來計算 a 和 b 的乘積。當 a 或 b 發(fā)生變化時,useMemo 會重新計算結果。否則,它將返回上一次計算的值,避免了不必要的計算。
useCallback
useCallback 是另一個用于優(yōu)化性能的 React 鉤子。它可以幫助我們避免在組件重新渲染時創(chuàng)建新的函數實例。useCallback 接受兩個參數:一個函數和一個依賴數組。當依賴數組中的值發(fā)生變化時,useCallback 會返回一個新的函數實例。否則,它將返回上一次創(chuàng)建的函數實例。
再看一個簡單的例子:
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>
);
}
在這個例子中,我們創(chuàng)建了一個名為 ButtonComponent 的組件,它接受一個 onClick 函數屬性。我們還創(chuàng)建了一個名為 ParentComponent 的組件,它使用 useCallback 鉤子來創(chuàng)建一個 handleClick 函數。當 ParentComponent 重新渲染時,useCallback 會返回上一次創(chuàng)建的 handleClick 函數實例,避免了不必要的函數創(chuàng)建。
memo
memo 是一個用于優(yōu)化性能的 React 高階組件。它可以幫助我們避免在父組件重新渲染時重新渲染子組件。memo 接受一個組件作為參數,并返回一個新的組件。當新組件的屬性發(fā)生變化時,它會重新渲染。否則,它將跳過渲染并返回上一次渲染的結果。
繼續(xù)舉例子:
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>
);
}
在這個例子中,我們創(chuàng)建了一個名為 ChildComponent 的組件,并使用 memo 高階組件對其進行了優(yōu)化。我們還創(chuàng)建了一個名為 ParentComponent 的組件,它可以切換 ChildComponent 的顯示。當 ParentComponent 重新渲染時,ChildComponent 的屬性沒有發(fā)生變化,因此它不會重新渲染。
區(qū)別
用法都很清楚了,接下來總結一下它們之間的區(qū)別:
useMemo用于避免在組件重新渲染時執(zhí)行昂貴的計算,只有在依賴發(fā)生變化時重新計算值。useCallback用于避免在組件重新渲染時創(chuàng)建新的函數實例,只有在依賴發(fā)生變化時返回新的函數實例。memo用于避免在父組件重新渲染時重新渲染子組件,只有在屬性發(fā)生變化時重新渲染組件。
雖然這些功能都可以幫助我們優(yōu)化性能,但它們的使用場景和工作原理有所不同。在實際開發(fā)中,需要因地制宜合理選用。
源碼分析
為了更深入地了解 useMemo、useCallback 和 memo 的工作原理,我們將繼續(xù)分析 React 18 的源碼。我們將關注這些功能的核心邏輯,并詳細解釋它們的功能。
調度器
眾所周知,在React hooks的體系中,每個鉤子都有自己各個階段的執(zhí)行邏輯,并且存到對應的Dispatcher中。
就拿useMemo來舉例:
// 掛載時的調度器
const HooksDispatcherOnMount: Dispatcher = {
// useMemo 掛載時的執(zhí)行函數
useMemo: mountMemo,
// other hooks...
};
// 數據更新時的調度器
const HooksDispatcherOnUpdate: Dispatcher = {
// useMemo 掛載時的執(zhí)行函數
useMemo: updateMemo,
// other hooks...
};
// 其他生命周期調度器...
上面代碼可以看出,useMemo 在掛載時執(zhí)行了的是 mountMemo, 而在更新數據時執(zhí)行的是 updateMemo。但為了更好了解 useMemo、useCallback 和 memo 的區(qū)別,我們只看更新部分就足夠了。
useMemo 源碼分析
源碼在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 的實現(xiàn)中,有一個關鍵函數 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。它首先檢查兩個數組的長度是否相等,如果不相等,將在開發(fā)模式下發(fā)出警告。然后,它遍歷數組并使用 is 函數(類似于 Object.is)逐個比較元素。如果發(fā)現(xiàn)任何不相等的元素,函數將返回 false。否則,返回 true。
這個函數在 useMemo 的實現(xiàn)中起到了關鍵作用,因為它決定了是否需要重新計算值。如果依賴項數組相等,useMemo 將返回上一次計算的值;否則,它將執(zhí)行 nextCreate 函數并返回一個新的值。
useCallback 源碼分析
由于 useCallback 和 useMemo 實現(xiàn)一致,其原理都是通過areHookInputsEqual 函數進行依賴項比對,區(qū)別在于 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 源碼分析
在 memo 的實現(xiàn)中,有一個關鍵函數 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 函數來阻止組件重新渲染。否則,它將繼續(xù)渲染組件并返回結果。
bailoutOnAlreadyFinishedWork 函數的實現(xiàn)位于同一個文件中,它的核心邏輯如下:
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,從而阻止組件重新渲染。
總結
在這篇文章中,我們深入分析了 React 18 中的 useMemo、useCallback 和 memo 功能的源碼。希望這篇文章能幫助你更好在實際項目中應用它們。
以上就是深入React 18源碼useMemo useCallback memo用法及區(qū)別分析的詳細內容,更多關于React useMemo useCallback memo的資料請關注腳本之家其它相關文章!
相關文章
React Native 使用Fetch發(fā)送網絡請求的示例代碼
本篇文章主要介紹了React Native 使用Fetch發(fā)送網絡請求的示例代碼,具有一定的參考價值,感興趣的小伙伴們可以參考一下2017-12-12
React使用highlight.js Clipboard.js實現(xiàn)代碼高亮復制
這篇文章主要為大家介紹了React使用highlight.js Clipboard.js實現(xiàn)代碼高亮復制功能示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2023-04-04
React-Native實現(xiàn)ListView組件之上拉刷新實例(iOS和Android通用)
本篇文章主要介紹了React-Native實現(xiàn)ListView組件之上拉刷新實例(iOS和Android通用),具有一定的參考價值,有興趣的可以了解一下2017-07-07

