React.memo 實(shí)現(xiàn)原理解析
核心實(shí)現(xiàn)概念
React.memo 的本質(zhì)是一個(gè)高階組件(HOC),它通過比較前后 props 來決定是否跳過組件的重新渲染。
1. 基本結(jié)構(gòu)
在 React 源碼中,React.memo 的實(shí)現(xiàn)大致如下:
function memo(type, compare) {
// 創(chuàng)建一個(gè)特殊的元素類型
const elementType = {
$$typeof: REACT_MEMO_TYPE, // 特殊標(biāo)識(shí)符,表示這是一個(gè) memo 組件
type, // 被包裹的原始組件
compare: compare === undefined ? null : compare, // 自定義比較函數(shù)
};
return elementType;
}
2. 在協(xié)調(diào)(Reconciliation)過程中的處理
當(dāng) React 遇到 REACT_MEMO_TYPE 類型的元素時(shí),會(huì)執(zhí)行特殊處理:
// 簡化版的協(xié)調(diào)邏輯
function updateMemoComponent(
currentFiber, // 當(dāng)前 Fiber 節(jié)點(diǎn)
workInProgressFiber, // 工作中的 Fiber 節(jié)點(diǎn)
Component, // 被 memo 包裹的組件
nextProps, // 新的 props
renderLanes // 渲染優(yōu)先級(jí)
) {
// 獲取之前的 props
const current = currentFiber.memoizedProps;
// 決定使用哪種比較函數(shù)
let compare = Component.compare;
if (compare === null) {
// 默認(rèn)使用淺比較
compare = shallowCompare;
}
// 檢查 props 是否相等
if (compare(current, nextProps)) {
// Props 沒有變化,完全跳過渲染
// 復(fù)用之前的子節(jié)點(diǎn)
return bailoutOnAlreadyFinishedWork(
currentFiber,
workInProgressFiber,
renderLanes
);
}
// Props 發(fā)生了變化,繼續(xù)正常渲染流程
return updateFunctionComponent(
currentFiber,
workInProgressFiber,
Component,
nextProps,
renderLanes
);
}
深入實(shí)現(xiàn)細(xì)節(jié)
1. 淺比較 (shallowCompare) 的實(shí)現(xiàn)
React 使用的默認(rèn)淺比較函數(shù)類似于:
function shallowCompare(prevProps, nextProps) {
// 如果 props 對(duì)象引用相同,直接返回 true
if (prevProps === nextProps) {
return true;
}
// 檢查 props 鍵的數(shù)量
const prevKeys = Object.keys(prevProps);
const nextKeys = Object.keys(nextProps);
if (prevKeys.length !== nextKeys.length) {
return false;
}
// 逐個(gè)比較每個(gè)屬性值
for (let i = 0; i < prevKeys.length; i++) {
const key = prevKeys[i];
// 使用 Object.is 進(jìn)行嚴(yán)格比較(類似于 ===,但處理了 NaN 和 +0/-0 的情況)
if (!Object.is(prevProps[key], nextProps[key])) {
return false;
}
}
return true;
}
2. Fiber 架構(gòu)中的優(yōu)化機(jī)制
在 React 的 Fiber 架構(gòu)中,React.memo 的優(yōu)化是通過以下機(jī)制實(shí)現(xiàn)的:
- 提前中止渲染:在
beginWork階段,如果檢測(cè)到是 memo 組件且 props 未變化,React 會(huì)立即中止當(dāng)前組件的渲染工作。 - 子樹復(fù)用:通過
bailoutOnAlreadyFinishedWork函數(shù),React 會(huì)標(biāo)記整個(gè)子樹為"無需更新",直接復(fù)用之前的渲染結(jié)果。 - 跳過副作用:由于組件沒有重新渲染,相關(guān)的副作用(如 useEffect)也不會(huì)被觸發(fā)。
3. 與 React 渲染流程的整合
// 簡化的渲染流程
function beginWork(currentFiber, workInProgressFiber, renderLanes) {
// 檢查是否是 memo 組件
if (workInProgressFiber.type && workInProgressFiber.type.$$typeof === REACT_MEMO_TYPE) {
return updateMemoComponent(
currentFiber,
workInProgressFiber,
workInProgressFiber.type.type, // 提取原始組件
workInProgressFiber.pendingProps,
renderLanes
);
}
// 處理其他類型的組件...
}
性能考慮與實(shí)現(xiàn)優(yōu)化
1. 記憶化策略
React.memo 的實(shí)現(xiàn)采用了記憶化(Memoization)策略:
- 存儲(chǔ)之前的結(jié)果:React 會(huì)存儲(chǔ)組件上一次的渲染結(jié)果(在 Fiber 節(jié)點(diǎn)的
memoizedState和memoizedProps中) - 比較成本與渲染成本的權(quán)衡:淺比較的計(jì)算成本遠(yuǎn)低于組件的渲染成本(包括虛擬 DOM 創(chuàng)建和差異比較)
2. 選擇性優(yōu)化
React 不會(huì)對(duì)所有組件都應(yīng)用 memo 優(yōu)化,因?yàn)椋?/p>
- 比較本身有成本
- 對(duì)于頻繁更新或 props 經(jīng)常變化的組件,memo 可能帶來負(fù)收益
3. 與其他優(yōu)化機(jī)制的協(xié)同
React.memo 與 React 的其他優(yōu)化機(jī)制協(xié)同工作:
- Context 優(yōu)化:即使使用
React.memo,如果組件消費(fèi)的 Context 值發(fā)生變化,組件仍會(huì)重新渲染 - 狀態(tài)更新優(yōu)化:組件內(nèi)部的狀態(tài)更新不受
React.memo影響
實(shí)際應(yīng)用中的實(shí)現(xiàn)考慮
1. 自定義比較函數(shù)的高級(jí)用法
// 深度比較實(shí)現(xiàn)(不推薦在生產(chǎn)環(huán)境使用,僅作示例)
function deepCompare(prevProps, nextProps) {
return JSON.stringify(prevProps) === JSON.stringify(nextProps);
}
// 選擇性比較
function selectiveCompare(prevProps, nextProps) {
// 只比較我們關(guān)心的屬性
return prevProps.importantValue === nextProps.importantValue;
}
const ExpensiveComponent = React.memo(
function ExpensiveComponent(props) {
// 組件實(shí)現(xiàn)
},
selectiveCompare // 使用自定義比較函數(shù)
);
2. 與 Hooks 的交互
React.memo 的實(shí)現(xiàn)需要考慮與 Hooks 的交互:
function MyComponent(props) {
// 即使使用 React.memo,內(nèi)部狀態(tài)變化仍會(huì)導(dǎo)致重新渲染
const [state, setState] = useState(0);
// 使用 useMemo 和 useCallback 可以進(jìn)一步優(yōu)化
const computedValue = useMemo(() => {
return expensiveCalculation(props.someValue);
}, [props.someValue]);
const handleClick = useCallback(() => {
// 處理點(diǎn)擊
}, []);
return <div>{/* ... */}</div>;
}
export default React.memo(MyComponent);
總結(jié)
React.memo 的實(shí)現(xiàn)原理可以概括為:
- 高階組件包裝:通過創(chuàng)建特殊類型的 React 元素標(biāo)記 memo 組件
- 協(xié)調(diào)階段攔截:在協(xié)調(diào)過程中識(shí)別 memo 組件并執(zhí)行特殊處理
- Props 比較:使用淺比較或自定義比較函數(shù)判斷 props 是否變化
- 渲染優(yōu)化:如果 props 未變化,跳過組件的渲染和子樹的協(xié)調(diào)過程
- 結(jié)果復(fù)用:直接復(fù)用之前的渲染結(jié)果,避免不必要的計(jì)算和 DOM 操作
這種實(shí)現(xiàn)方式體現(xiàn)了 React 性能優(yōu)化的核心思想:用較小的比較成本換取可能很大的渲染成本節(jié)約。
到此這篇關(guān)于React.memo 實(shí)現(xiàn)原理解析的文章就介紹到這了,更多相關(guān)React.memo實(shí)現(xiàn)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
淺析react里面如何封裝一個(gè)通用的Ellipsis組件
這篇文章主要為大家詳細(xì)介紹了在react里面如何封裝一個(gè)通用的Ellipsis組件,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-12-12
react16+antd4 RangePicker組件實(shí)現(xiàn)時(shí)間禁選示例
這篇文章主要為大家介紹了react16+antd4 RangePicker 時(shí)間禁選示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-05-05
React前端開發(fā)createElement源碼解讀
這篇文章主要為大家介紹了React前端開發(fā)createElement源碼解讀,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-11-11
詳解React Native 采用Fetch方式發(fā)送跨域POST請(qǐng)求
這篇文章主要介紹了詳解React Native 采用Fetch方式發(fā)送跨域POST請(qǐng)求,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11
React Native提供自動(dòng)完成的下拉菜單的方法示例
這篇文章主要為大家介紹了React Native提供自動(dòng)完成的下拉菜單的方法示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-10-10
使用react實(shí)現(xiàn)手機(jī)號(hào)的數(shù)據(jù)同步顯示功能的示例代碼
本篇文章主要介紹了使用react實(shí)現(xiàn)手機(jī)號(hào)的數(shù)據(jù)同步顯示功能的示例代碼,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-04-04

