1 前言
大家好,我是心鎖,一枚23屆準畢業生,
如果讀者閱讀過我其他幾篇React相關的文章,就知道這次我是來填坑的了
原因是,寫了兩篇解讀react-hook的文章后我發現——并不是每位同學都清楚React的架構,包括我在內也只是綜合不同技術文章與閱讀部分原始碼有一個了解,但是除錯時真正沉淀成文章的還沒有,
所以這篇文章來啦~文章基于2022年八九月的React原始碼進行除錯及閱讀,將以通俗的形式揭秘React
閱讀本文,成本與收益如下
閱讀耗時:26min+
全文字數:1w+
全文字符:5.5w+
預期收益:通明境 · React架構
本文適合有閱讀React原始碼計劃的初學者或者正在閱讀React原始碼的工程師,我們一起形成頭腦風暴,
2 認識Fiber節點
2.1 Fiber節點基礎部分
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
// Instance
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;
...
this.ref = null;
...
}
Fiber節點本身存盤了一些最基本的資料,其中包括如上六項構成Instance,它們分別代表
-
tag:Fiber節點對應組件的型別,包括了Funtion、Class等
-
key:更新key會強制更新Fiber節點
-
type:保存組件本身,準確來說,對于函陣列件保存函式本身,對于類組件保存類本身,對于HostComponent,也就是如原生<div></div>這類原生標簽會保存節點名稱
-
elementType:保存組件型別和type大部分情況是一樣的,但是也有不一樣的情況,比如
LazyComponent -
stateNode:保存Fiber對應的真實DOM節點
-
ref: 和key一樣屬于base欄位
2.2 Fiber樹結構實作
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
...
// Fiber
this.return = null;
this.child = null;
this.sibling = null;
this.index = 0;
...
}
我們看到Fiber節點這四個屬性,它們的含義分別是
- return:指向父節點Fiber
- child:指向子節點Fiber
- sibling:指向右邊的兄弟節點Fiber
這樣子一來,對于我們這里的組件,就構成了如圖的Fiber樹
const CountButton = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(v => v + 1);
};
useEffect(() => {
console.log('Hello Mount Effect');
return () => {
console.log('Hello Unmount Effect');
};
}, []);
useEffect(() => {
console.log('Hello count Effect');
}, [count]);
return (
<>
<div>Render by state</div>
<div>{count}</div>
<button onClick={handleClick}>Add Count</button>
</>
);
};
function App() {
return (
<div className="App">
<header className="App-header">
<img src=https://www.cnblogs.com/SourceHeartLock/archive/2022/09/19/{logo} className="App-logo" alt="logo" />
<CountButton/>
</header>
</div>
);
}
2.3 函式式組件&&Fiber
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
...
this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;
...
}
從原始碼上看,React為hook足足騰出了五個屬性專門處理在函式式組件中使用hook的場景,
這些個玩意兒氣其實我們在前邊的hook章節也或多或少有了解過,這里專門講述Fiber節點上存盤的這些結構的作用,
2.3.1 pendingProps
pendingProps,從FiberNode的建構式看,是mixed(可傳入)進來的
也就是說,這部分props可以在Fiber間傳遞,主要用于更新/創造新Fiber節點時用來傳遞props
2.3.2 memoizedProps
memoizedProps和pendingProps的區別是什么呢?
我們知道,props代表一個Function的引數,當props變化時Function也會再次執行,
一般來講,memoizedProps會在整個渲染流程結尾部分被更新,存盤FiberNode的props,
而pendingProps一般在渲染開始時,作為新的Props出現
舉個更便于理解的例子,在如圖的beginWork階段,會對比新的props和舊的props來確定是否更新,此時比較的就是workInProgress.pendingProps和current.memoizedProps
2.3.3 updateQueue
上一篇我們講useEffect有講到,updateQueue以如圖的形式存盤useEffect運行時生成的各個effect
lastEffect以環形鏈的形式存盤了單個節點的所有effect,
(當然,這里指的當然只是函式式組件)
2.3.4 memoizedState
在useState章節,我們也有講過memoizedState,memoizedState存盤了我們呼叫hook時產生的hook物件,目前已知除了useContext不會有hook物件產生并掛載,其他hook都會掛載到這里,
hook之間以.next相連形成單向鏈表,
而hook呼叫時產生的不管是effect(useEffect)還是state(useState),都是存盤在
hook.memoizedState,體現在Fiber節點上,其實是存盤在hook.memoizedState.memoizedState,注意不要混淆,
2.3.5 dependencies
以下是除錯代碼
const BaseContext = createContext(1);
const BaseContextDemo = () => {
const {base} = useContext(BaseContext);
return <div>{base}</div>;
};
const CountButton = () => {
const [count, setCount] = useState(0);
const handleClick = () => {
setCount(v => v + 1);
};
useEffect(() => {
console.log('Hello Mount Effect');
return () => {
console.log('Hello Unmount Effect');
};
}, []);
useEffect(() => {
console.log('Hello count Effect');
}, [count]);
const ref = useRef();
const [base, setBase] = useState(null);
const initValue = https://www.cnblogs.com/SourceHeartLock/archive/2022/09/19/{
base,
setBase,
};
return (
Render by state
{count}
);
};
在還沒有發出的useContext原理中,會記載useContext的實作原理,劇透就是FiberNode.dependencies這個屬性記載了組件中通過useContext獲取到的背景關系
從除錯結果看,多個context也將通過.next相連,同時顯然,這是一條單向鏈表
2.4 操作依據
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
...
// Effects
this.flags = NoFlags;
this.subtreeFlags = NoFlags;
this.deletions = null;
...
}
我們看到這三個屬性
-
deletions:待洗掉的子節點,render階段diff演算法如果檢測到Fiber的子節點應該被洗掉就會保存到這里,
-
flags/subtreeFlags:都是二進制形式,分別代表
Fiber節點本身的保存的操作依據與Fiber節點的子樹的操作依據,
flags是React中很重要的一環,具體作用是通過二進制在每個Fiber節點保存其本身與子節點的flags,
至于具體如何保存,實際上是使用了二進制的特性,舉幾個例子
2.4.1 &運算
溫習一下
&運算子的規則:只有1&1=1,其他情況為0
const NoFlags = /* */ 0b000000000000000000000000;
const PerformedWork = /* */ 0b000000000000000000000001;
const Placement = /* */ 0b000000000000000000000010;
const Update = /* */ 0b000000000000000000000100;
const unknownFlags=Placement;
Boolean(unknownFlags & Placement) // true
Boolean(unknownFlags & Update) //false
React中會用一個未知的flags & 一個flag,此時是在判斷未知的flags中是否包含flag,
之所以說是是否包含,我們可以看看下邊的代碼,
const NoFlags = /* */ 0b000000000000000000000000;
const PerformedWork = /* */ 0b000000000000000000000001;
const Placement = /* */ 0b000000000000000000000010;
const Update = /* */ 0b000000000000000000000100;
const unknownFlags = Placement|Update; //此時=0b000000000000000000000110
Boolean(unknownFlags & Placement) // true
Boolean(unknownFlags & Update) //true
2.4.2 |運算
溫習一下
|運算子的規則:只有0&0=0,其他情況為1
上邊unknownFlags的例子我們不難發現,react利用了|運算子的特性來存盤flag
const unknownFlags = Placement|Update; //此時=0b000000000000000000000110
這樣的好處是快,判斷是否包含的時候,直接使用& 運算子,在有限的操作依據面前,使用二進制完全可以兜住所有情況,
2.4.3 ~運算
~運算子會把每一位取反,即1->0,0->1
在React中,~運算子同樣是常用操作
那么作用是什么呢?其實也很容易從函式背景關系分析出來,對于圖中這個例子,react通過~運算子與&運算子的結合,從flags中洗掉了Placement這個flag,
2.4.4 小總結:React中常見的操作
-
通過
unknownFlags & Placement判斷unknownFlags是否包含Placement -
通過
unknownFlags |= Placement將Placement合并進unknownFlags中 -
通過
unknownFlags &= ~Placement將Placement從unknownFlags中刪去
關于有哪些flags,我們可以翻閱到
ReactFiberFlags.js,這里會有詳細flags的記載![]()
2.5 雙快取樹的體現
我們曾說過,React的最基本作業原理雙快取樹,這引申出了我們需要知道這種機制在React中的實際體現,
這需要我們找到ReactFiber.old.js
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
...
this.alternate = null;
...
}
由此我們知道,FIberNode上會有一個屬性alternate,而這個屬性正是我們期望的雙快取樹中,里樹與外樹的雙向指標,
正如圖所見,在初次渲染中,current===null,所以目前仍是白屏,而workInProgress已經在構建
(圖誤,在renderWithHooks才對)
而當我們再次渲染,在renderWithHooks斷點,就可以觀察到workInProgress.alternate==current
2.6* 優先級相關
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
...
this.lanes = NoLanes;
this.childLanes = NoLanes;
...
}
和lane有關的變數統一和調度優先級有關,暫時不涉及(因為還沒看)
2.7* React devtools Profiler
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
...
if (enableProfilerTimer) {
this.actualDuration = Number.NaN;
this.actualStartTime = Number.NaN;
this.selfBaseDuration = Number.NaN;
this.treeBaseDuration = Number.NaN;
this.actualDuration = 0;
this.actualStartTime = -1;
this.selfBaseDuration = 0;
this.treeBaseDuration = 0;
}
...
}
React并不只是react,react倉庫里包含了其他工程,其中就包含了我們的react profiler工具,在使用了profiler工具的情況下,react fiber會記錄一些運行時間,其實很多帶有Profiler的判斷陳述句都是和Profiler在配合,
3 好好認識hook結構
我們上邊有講到FiberNode.memoizedState,我們知道這里保存的是mountWorkInProgressHook時產生的hook物件
{
memoizedState: 0,
baseState: 0,
baseQueue: null,
queue: ???,
next:null
}
那么hook的各個項指什么?
3.1 baseState和memoizedState
其實很好理解,baseState對應上一次的state(effect),memoizedState為最新的state(effect),總之就是hook保存基本資料的地方,
3.2 queue
而hook.queue則是useState、useReducer的dispatcher存盤的地方,
var queue:UpdateQueue = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: reducer,
lastRenderedState: initialState
};
hook.queue = queue;
var dispatch = queue.dispatch = dispatchReducerAction.bind(null, currentlyRenderingFiber$1, queue);
對于queue的結構,我們逐一講解
3.2.1 lastRenderedState & lastRenderedReducer
- queue.lastRenderedState屬性存盤上一個 state
- queue.lastRenderedReducer 屬性存盤 reducer 內部狀態變更邏輯
其中queue.lastRenderedReduce可能不好理解,我們可以從代碼中理解,且看這里
function basicStateReducer(state, action) {
// $FlowFixMe: Flow doesn't like mixed types
return typeof action === 'function' ? action(state) : action;
}
function mountState(initialState) {
...
hook.memoizedState = hook.baseState = initialState;
var queue = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState
};
...
}
這是dispatchSetState中的一段邏輯,處理的正是我們下邊將講述的,「不在渲染中」的處理階段(onClick觸發===異步觸發),
那這里可以看到,我們可以從lastRenderedReducer得到eagerState
var currentState = queue.lastRenderedState;
var eagerState = lastRenderedReducer(currentState, action); // Stash the eagerly computed state, and the reducer used to compute
// it, on the update object. If the reducer hasn't changed by the
// time we enter the render phase, then the eager state can be used
// without calling the reducer again.
eagerState是什么? 實際上這里是通過lastRenderedReducer快速獲得了最近一次的state,
react會通過objectIs(eagerState,currentState)來確定是否不進行更新,這也是為什么我們更新state的時候要注意state為不可變資料,每次更新都需要更新一個新值才有效
if (objectIs(eagerState, currentState)) {
enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
return;
}
3.2.2 dispatch
dispatch 屬性存盤狀態變更函式,對應useState、useReducer 回傳值中的第二項
function mountState(initialState) {
var hook = mountWorkInProgressHook();
if (typeof initialState === 'function') {
initialState = initialState();
}
hook.memoizedState = hook.baseState = initialState;
var queue = {
pending: null,
lanes: NoLanes,
dispatch: null,
lastRenderedReducer: basicStateReducer,
lastRenderedState: initialState
};
hook.queue = queue;
var dispatch = queue.dispatch = dispatchSetState.bind(null, currentlyRenderingFiber$1, queue);
return [hook.memoizedState, dispatch];
}
值得注意的就是dispatch會通過.bind事先注入currentlyRenderingFiber$1, queue兩個引數,此間通過bind系結的currentlyRenderingFiber$1,作用是判斷這個更新是在fiber的render階段還是異步觸發,
這也給了我們一個判斷fiber在render階段的條件
function isRenderPhaseUpdate(fiber: Fiber) { const alternate = fiber.alternate; return ( fiber === currentlyRenderingFiber || (alternate !== null && alternate === currentlyRenderingFiber) ); }
3.2.3 pending
pending 屬性存盤排隊中的狀態變更規則,單向環形鏈表結構,
在原始碼中,每一個規則以Update的結構連接
export type Update<S, A> = {|
lane: Lane,
action: A,
hasEagerState: boolean,
eagerState: S | null,
next: Update<S, A>,
|};
那么我們知道了
- eagerState 快取上一個狀態(React稱之為急迫的狀態)
- action 代表狀態變更的規則,可以是本次要被修改的值,也可以是函式
- hasEagerState 則是記錄是否執行過優化邏輯
eagerState在所有原始碼中只在這里使用,根據React原始碼,這里的優化指的是React會在eagerState===currentState的情況下,不做重渲染,如果狀態更新前后沒有變化,則可以略過剩下的步驟,
try {
var currentState = queue.lastRenderedState;
var eagerState = lastRenderedReducer(currentState, action);
update.hasEagerState = true;
update.eagerState = eagerState;
if (objectIs(eagerState, currentState)) {
enqueueConcurrentHookUpdateAndEagerlyBailout(fiber, queue, update);
return;
}
} catch (error) {
} finally {
{
ReactCurrentDispatcher$1.current = prevDispatcher;
}
}
3.3 baseQueue
值得注意的是,baseQueue的結構來自queue.pending而不是queue
(baseQueue被賦值queue.pending)
其余的大抵是沒啥好說的,baseQueue在除錯中的體現我暫時并沒有遇到,推測需要有比較大量的更新,
4 React架構
本章我們講述React的渲染流程,將覆寫React的render階段與commit階段的概念與流程概覽,不會非常深入,爭取留存印象,
4.1 React渲染關鍵節點
我們已經預先知道可以將React的渲染分成render階段和commit階段,也知道render階段的關鍵函式是beginWork和completeWork,commit階段的關鍵函式則是commitRoot,
在這個基礎上,我們從呼叫堆疊中可以找到這兩個階段的起始節點,
- render階段
我們在beginWork中打上斷點,然后可以回溯呼叫堆疊找到出發點,
從圖中,我們可以知道renderRoot觸發于performConcurrentWorkOnRoot
除此之外,在performSyncWorkOnRoot中也可以走入renderRoot
它們會根據情況走到renderRootConcurrent或者renderRootSync,這里即是render階段的開始點
那么我們得到第一個關鍵節點:
- render階段開始于
renderRootConcurrent或renderRootSync
- commit階段
我們知道,render階段的尾巴是completeWork,commit階段的起步是commitRoot,我們嘗試在這completeWork方法中斷點,然后單步除錯到commitRoot,
上圖是我debug出來的結果,completeWork與commitRoot之間的最近公共函式節點是performSyncWorkOnRoot/performConcurrentWorkOnRoot,
那么我們知道,commitRoot即是commit階段的起點,
那么我們得到兩個關鍵資訊:
- commit階段開始于
commitRoot- render階段和commit階段通過
performSyncWorkOnRoot/performConcurrentWorkOnRoot聯動
4.1.1 小總結
- render階段開始于
renderRootConcurrent或renderRootSync - commit階段開始于
commitRoot - render階段和commit階段通過
performSyncWorkOnRoot/performConcurrentWorkOnRoot聯動
4.2 狀態更新流程
4.2.1 找到root節點
正常render的第一步,是找到當前Fiber的root節點,
以useState造成的渲染舉例,React會通過enqueueConcurrentHookUpdate->getRootForUpdatedFiber找到當前節點的root節點,
function dispatchSetState(fiber, queue, action) {
...
var root = enqueueConcurrentHookUpdate(fiber, queue, update, lane);
if (root !== null) {
var eventTime = requestEventTime();
scheduleUpdateOnFiber(root, fiber, lane, eventTime);
entangleTransitionUpdate(root, queue, lane);
}
...
}
function getRootForUpdatedFiber(sourceFiber) {
...
detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber);
var node = sourceFiber;
var parent = node.return;
while (parent !== null) {
detectUpdateOnUnmountedFiber(sourceFiber, node);
node = parent;
parent = node.return;
}
return node.tag === HostRoot ? node.stateNode : null;
}
尋找root節點是一個向上不斷尋找root節點的程序,在這個程序中react還會持續呼叫detectUpdateOnUnmountedFiber檢查是否呼叫了過期的更新函式,
什么是過期的更新函式?舉個例子,通過useRef保存了setState方法,但是隨著組件更新ref中的setState方法并沒有更新,此時由于setState方法本質上是通過.bind的形式報存了函式及引數fiber節點,此時就會存在呼叫了一個已卸載組件的過期的setState方法,
4.2.2 調度同步/異步更新
找到root節點之后,那么就要進入render流程,這就存在一個問題,
我們上邊說了,render階段的觸發函式是performSyncWorkOnRoot或performConcurrentWorkOnRoot,那么如何判斷應該進入同步更新還是異步更新呢?
這就要走到ensureRootIsScheduled,ensureRootIsScheduled會通過判斷newCallbackPriority === SyncLane來確定走同步render還是異步render,這里涉及調度器,暫時不講(還沒看還不會)
function ensureRootIsScheduled(root, currentTime) {
...
var newCallbackNode;
if (newCallbackPriority === SyncLane) {
// Special case: Sync React callbacks are scheduled on a special
// internal queue
if (root.tag === LegacyRoot) {
...
scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
} else {
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
}
...
newCallbackNode = null;
} else {
var schedulerPriorityLevel;
...
newCallbackNode = scheduleCallback$2(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));
}
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
那么可以看到,這里會有一個scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root))或者scheduleCallback$2(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root))的程序,
值得注意的是,同步調度這里還更復雜,react一方面需要考慮是否是嚴格模式做不同的callback
(ensureRootIsScheduled是一個很重要的函式,會Scheduled一起講會比較好)
另一方面還調度了flushSynCallbacks,這個函式做的事情很簡單,就是把syncQueue中的待執行任務全部執行
4.2.3 render階段
render階段分成了兩個階段,我們在狀態更新流程中不講細節,只講明基本作用,細節請看后邊的單章
經歷了調度更新,會來到render階段,render階段做了兩件事,
beginWork階段,在這個階段react做的事情是從root遞回到子葉,每次beginWork會對Fiber節點進行新建/復用邏輯,然后通過reconcileChildren將child Fiber掛載到workInProgress.child并在child Fiber上記錄flags,最終遍歷整個Fiber樹completeWork階段,在這個階段,是從子葉不斷向上遍歷到父親Fiber節點的程序,這個程序中,completeWork會把workInProgress Tree上的真實DOM掛載/更新上去,
那么總結來說,beginWork負責虛擬DOM節點Fiber Node的維護與flag記錄,completeWork負責真實DOM節點在Fiber Node的映射作業,
當然,這些操作只涉及節點維護,真正渲染到頁面上就是commit階段要負責的了
4.2.4 commit階段
commit階段,除了會處理一下和hook相關的事情之外,最主要做了就是負責把beginWork階段記錄的flags在真實DOM樹上進行操作,
總結來說:
- 處理和
useEffect\useInsertionEffect\useLayoutEffect相關的hook,處理class組件相關的生命周期鉤子 - 基于flags做真實DOM樹操作,包括增刪改,以及輸入框型別節點的focus、blur等問題
- 清理一些全域變數,并確保進入下一次調度
4.3 render階段
這里是延續狀態更新流程的render階段,
我們在狀態更新第一步就拿到了root節點,經過調度更新后會進入render階段,
此時我們有兩種走法,一種是通過renderRootSync來到workLoopSync,另一種則是通過renderRootConcurrent走到workLoopConcurrent,這兩者的區別是workLoopConcurrent會檢查瀏覽器是否有剩余時間片,
function workLoopConcurrent() {
// 執行作業,直到調度程式要求我們讓步
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
function workLoopSync() {
// 已經超時了,因此無需檢查我們是否需要讓步就可以執行作業
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
workLoop做了什么呢?這就要從performUnitOfWork(workInProgress)說起,下邊的代碼是精簡邏輯 (只剩下beginWork這部分邏輯) 過后的performUnitOfWork函式,可以看到performUnitOfWork通過beginWork創建了一個新的節點賦給workInProgress,
function performUnitOfWork(unitOfWork) {
var current = unitOfWork.alternate; // currentFiber
setCurrentFiber(unitOfWork); // 會將全域current變數設定為workInProgressFiber
var next = beginWork$1(current, unitOfWork, renderLanes$1); // currentFiber
resetCurrentFiber(); // 重置current變數為null
unitOfWork.memoizedProps = unitOfWork.pendingProps;
workInProgress = next;
...
}
4.3.1 beginWork
那么此處引出了render階段中最重要的兩個函式之一beginWork,beginWork正如上邊所說,這個函式的職責是回傳一個Fiber節點,這個節點可以復用currentFiber也可以創建一個新的,
我們其實在【useState原理】章節中有見過beginWork,當時我們強調了雙快取機制,這次我們可以更細地了解一下beginWork,
我們提煉一下beginWork的核心邏輯,會發現beginWork通過current!==null來判斷是否是第一次執行,這里的邏輯是如果是第一次執行,那么Fiber沒有mount,自然為null,
function beginWork(current, workInProgress, renderLanes) {
...
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasContextChanged() || (
workInProgress.type !== current.type )) {
didReceiveUpdate = true;
} else {
var hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(current, renderLanes);
if (!hasScheduledUpdateOrContext &&
(workInProgress.flags & DidCapture) === NoFlags) {
// 沒有待更新的updates或者背景關系資訊,復用上次的Fiber節點
didReceiveUpdate = false;
return attemptEarlyBailoutIfNoScheduledUpdate(current, workInProgress, renderLanes);
}
...
}
} else {
didReceiveUpdate = false;
...
}
workInProgress.lanes = NoLanes;
switch (workInProgress.tag) {
...
case FunctionComponent:
...
case HostComponent:
...
}
}
#1 update復用邏輯
看到這里,react在update的邏輯中,根據三個條件來判斷是否復用上一次的FIber
-
oldProps !== newProps,代表
props是否變化 -
hasContextChanged(),
var didPerformWorkStackCursor = createCursor(false); // Keep track of the previous context object that was on the stack. // We use this to get access to the parent context after we have already // pushed the next context provider, and now need to merge their contexts. -
workInProgress.type !== current.type,
fiber.type是否變化
function beginWork(current, workInProgress, renderLanes) {
...
if (current !== null) {
var oldProps = current.memoizedProps;
var newProps = workInProgress.pendingProps;
if (oldProps !== newProps || hasContextChanged() || (
workInProgress.type !== current.type )) {
didReceiveUpdate = true;
} else {
//此處是復用的邏輯
...
}
} else {
didReceiveUpdate = false;
...
}
...
}
#2 mount/update新建邏輯
不滿足更新條件的話,會根據workInProgress.tag新建不同型別的Fiber節點,對于不進行Fiber復用到更新也會進入這個邏輯
switch (workInProgress.tag) {
case IndeterminateComponent:
{
return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
}
case LazyComponent:
{
var elementType = workInProgress.elementType;
return mountLazyComponent(current, workInProgress, elementType, renderLanes);
}
case FunctionComponent:
{
var Component = workInProgress.type;
var unresolvedProps = workInProgress.pendingProps;
var resolvedProps = workInProgress.elementType === Component ? unresolvedProps : resolveDefaultProps(Component, unresolvedProps);
return updateFunctionComponent(current, workInProgress, Component, resolvedProps, renderLanes);
}
case ClassComponent:
{
var _Component = workInProgress.type;
var _unresolvedProps = workInProgress.pendingProps;
var _resolvedProps = workInProgress.elementType === _Component ? _unresolvedProps : resolveDefaultProps(_Component, _unresolvedProps);
return updateClassComponent(current, workInProgress, _Component, _resolvedProps, renderLanes);
}
...
}
根據我們在【useState】章節的識訓,不管是update還是mount都要走到reconcileChildren
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
// mount時
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
} else {
// update時
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
}
這里做的事情描述起來是比較好辦的,不過詳細起來就涉及diff演算法需要開單章
- mount時,創建新的Child Fiber節點
- update時,將當前組件與該組件在上次更新時對應的
Fiber節點進行diff比較,將比較的結果生成新Fiber節點
當然,不管走到哪里,workInProgress都會得到一個child FIber
不管是reconcileChildFibers還是mountChildFibers,都是通過呼叫ChildReconciler這個函式來運行的,
而在整個ChildReconciler中,我們會經常性看到如圖一樣的操作,
這便引出了操作依據一說,react用Fiber.flags并以二進制的形式存貯了對于每個Fiber的操作依據,這種方式比陣列更高效,可以方便地使用位運算發為Fiber.flags增刪不同的操作依據,
點擊這里可以查看所有的操作型別
#3 diff演算法*
標記這個知識點,下次再說
4.3.2 completeWork
我們持續執行workLoop,會發現workInProgress從rootFiber持續深入到了我的除錯代碼中的最底層(一個div),此時就到了render階段的第二個階段completeWork,
function performUnitOfWork(unitOfWork) {
...
if (next === null) {
// 進入completeWork
completeUnitOfWork(unitOfWork);
} else {
...
}
...
}
那么此時進入completeUnitOfWork,這里的核心邏輯是completeWork從子節點不斷訪問workInProgress.return向上回圈執行beginWork,如果遇到兄弟子節點,則會將workInProgress指向兄弟節點并回傳至performUnitOfWork,重新執行beginWork到completeWork的整個render階段,
那么completeWork做了什么?這里是completeWork的基本邏輯框架(我把bubbleProperties提出來方便理解每個completeWork都會執行這前后兩條陳述句),做了popTreeContext和bubbleProperties,
function completeWork(current, workInProgress, renderLanes) {
popTreeContext(workInProgress);
switch (workInProgress.tag) {
case FunctionComponent:
...
case HostComponent:
...
...
}
bubbleProperties(workInProgress);
}
popTreeContext是和上邊beginWork相關的內容,這里的目的是使得正在進行的作業不處于堆疊頂部,對應pushContext的階段一般在beginWork的swtich中進入的函式中都可以找到
而bubbleProperties的核心邏輯我也提了出來,可以看到這里是做了一個層遍歷,遍歷了completedWorkFiber的所有child,將它們的return賦值為completedWorkFiber,同時,這里也涉及了subtreeFlags的計算,會將子節點的操作依據冒泡到父節點,
而關于subtreeFlags的具體用處,在commit階段,我們后邊說,
function bubbleProperties(){
...
var newChildLanes = NoLanes;
var subtreeFlags = NoFlags;
{
var _child = completedWork.child;
while (_child !== null) {
newChildLanes = mergeLanes(newChildLanes, mergeLanes(_child.lanes, _child.childLanes));
subtreeFlags |= _child.subtreeFlags;
subtreeFlags |= _child.flags;
_child.return = completedWork;
_child = _child.sibling;
}
}
completedWork.subtreeFlags |= subtreeFlags;
}
...
}
后續的話,會根據workInProgress.tag來走不同的邏輯,我們這里主要說HostComponent的邏輯,代表原生組件,
下邊是我提煉出來的核心邏輯,這里同樣會區分update和mount,
function completeWork(current, workInProgress, renderLanes) {
popTreeContext(workInProgress);
switch (workInProgress.tag) {
...
case HostComponent:{
popHostContext(workInProgress);
var type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent$1(current, workInProgress, type, newProps);
...
} else {
...
var currentHostContext = getHostContext();
var rootContainerInstance = getRootHostContainer();
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
appendAllChildren(instance, workInProgress, false, false);
workInProgress.stateNode = instance;
...
}
bubbleProperties(workInProgress);
return null;
}
...
}
}
#1 update時
update時,無需生成新的DOM節點,所以此時要處理props,在updateHostComponent中,第二部分會呼叫prepareUpdate->diffProperties獲得一個updatePayload掛載在workInProgress.updateQueue上
具體會處理哪些props,我們深入到diffProperties就可以找到這一塊的邏輯
OK,那么我們回到上邊所說的updatePayload,除錯發現updatePayload是一個陣列,資料結構體現為一個偶數為key,奇數為value的陣列:
到了這一步,update流程最后會走入markUpdate,至此,completeWork的update邏輯完畢
#2 mount時
我們此時來看mount時的邏輯,這里最核心的邏輯簡化后其實只有幾句
function completeWork(current, workInProgress, renderLanes) {
popTreeContext(workInProgress);
...
var currentHostContext = getHostContext();
var rootContainerInstance = getRootHostContainer(); // 獲得root真實DOM
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);// 創建Fiber對應的真實DOM
appendAllChildren(instance, workInProgress, false, false);//將創建的真實dom插入workInProgressFiber
workInProgress.stateNode = instance;
...
bubbleProperties(workInProgress);
}
我們關注appendAllChildren,這里的邏輯是將新建的instance作為真實節點parent,將其插入到workInProgressFiber的真實節點中(因為一個Fiber節點不一定有真實節點,所以要找到可以插入的真實節點)
appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
var node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (node.tag === HostPortal) ; else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
};
那么這里實際做的就是把真實DOM掛載到workInProgressFiber上,又由于我們上邊說了,complateWork是一個從子節點向上遍歷的程序,那么遍歷完畢的時候,我們就得到了一顆構建好的workInProgress Tree
那么接著,就是commit階段了,
4.4 commit階段
首先我們要知道commit階段的職責是什么,
這樣的話,我們又要強調一下雙快取樹了,workInProgress樹是一顆在記憶體中構建的DOM樹,current樹則是頁面正在渲染的DOM樹,
在此基礎上,render階段已經完成了記憶體中構建下一狀態的workInProgress,那么此時commit階段正應該做將current樹與workInProgress樹調換的作業,
而調換作業中,由于render階段的真實DOM并沒有更新,只是做了標記,此時會需要commit階段負責把這些更新根據不同的操作標記在真實DOM上操作,
commit階段開始于commitRoot,往下就是呼叫commitRootImpl,我們會著重分析commitRootImpl
首先看入參,可以看到commitRootImpl的入參有四個,其中root為最基本的引數,傳入的是已準備就緒的workInProgressRootFiber,
function commitRootImpl(
root: FiberRoot,
recoverableErrors: null | Array<CapturedValue<mixed>>,
transitions: Array<Transition> | null,
renderPriorityLevel: EventPriority,
)
我們認為commit階段可以分為三個階段,分別代表
- before mutation,在執行DOM操作前的階段
- mutation,執行DOM操作
- layout,執行DOM操作之后
當然,在這些流程之外,commit階段還會處理useEffect這類需要在commit階段執行的hook,
4.4.1 Before commit start
在commit開始之前,即before mutation之前的代碼可以從下邊看見,它們具體做了什么我直接在代碼中注釋了,請看注釋,
function commitRootImpl(
root: FiberRoot,
recoverableErrors: null | Array<CapturedValue<mixed>>,
transitions: Array<Transition> | null,
renderPriorityLevel: EventPriority,
) {
do {
// 這里會調度未執行完的useEffect,之所以上下各有一處,一方面是和React優先級有關,一方面也和因為調度`useEffect`等hook時重新進入了render階段重新進入到commit階段有關,
flushPassiveEffects();
} while (rootWithPendingPassiveEffects !== null);
...
// 和flags類似的二進制
if ((executionContext & (RenderContext | CommitContext)) !== NoContext) {
throw new Error('Should not already be working.');
}
// finishedWork是已經處理好的workInProgressRootFiber
const finishedWork = root.finishedWork;
const lanes = root.finishedLanes;
...
if (finishedWork === null) {
return null;
}
//重置待commit的rootFiber,重置commit優先級
root.finishedWork = null;
root.finishedLanes = NoLanes;
...
// commitRoot總是同步完成
// 所以在這里清除Scheduler系結的回呼函式等變數允許系結新的函式
root.callbackNode = null;
root.callbackPriority = NoLane;
//一些優先級的計算
let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
const concurrentlyUpdatedLanes = getConcurrentlyUpdatedLanes();
remainingLanes = mergeLanes(remainingLanes, concurrentlyUpdatedLanes);
markRootFinished(root, remainingLanes);
if (root === workInProgressRoot) {
// 完成后,重置全域變數
workInProgressRoot = null;
workInProgress = null;
workInProgressRootRenderLanes = NoLanes;
}
// 當finishedWork中存在PassiveMask標記時,調度useEffect
if (
(finishedWork.subtreeFlags & PassiveMask) !== NoFlags ||
(finishedWork.flags & PassiveMask) !== NoFlags
) {
if (!rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = true;
pendingPassiveEffectsRemainingLanes = remainingLanes;
pendingPassiveTransitions = transitions;
scheduleCallback(NormalSchedulerPriority, () => {
// 這里會調度useEffect的運行,詳情請看【useEffect】篇
flushPassiveEffects();
return null;
});
}
}
...
}
這里有一點值得注意的是,伴隨著flushPassiveEffects的呼叫,在堆疊中完全可能形成多次commit,這是來源于useEffect的副作用觸發了組件渲染,在這種情況下會再走一次狀態更新流程(當然這期間有優化)
4.4.2 BeforeMutation
commit階段的正式開始,在于commitBeforeMutationEffects這個函式,可以看到當react確定subtreeFlags或者root.flags上可以找到BeforeMutationMask | MutationMask | LayoutMask | PassiveMask時,會觸發commit的邏輯
var subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
var rootHasEffect = (finishedWork.flags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags;
if (subtreeHasEffects || rootHasEffect) {
...
var shouldFireAfterActiveInstanceBlur = commitBeforeMutationEffects(root, finishedWork);
...
} else {
// No effects.
root.current = finishedWork;
}
那么我們首先來看commitBeforeMutationEffects,那么可以看到commitBeforeMutationEffects緊接著呼叫了commitBeforeMutationEffects_begin,
而commitBeforeMutationEffects_begin做的事情是從finishedWork向下遍歷fiber樹,一直到遍歷到某個Fiber節點不再有BeforeMutationMask標記,此時會進入commitBeforeMutationEffects_complete,
function commitBeforeMutationEffects(root, firstChild) {
// 處理焦點相關的邏輯,處理原因是因為真實DOM的增刪導致可能出現的焦點變化
focusedInstanceHandle = prepareForCommit(root.containerInfo);
// nextEffect是一個全域變數,firstChild對應上方傳參`finishedWork`
nextEffect = firstChild;
commitBeforeMutationEffects_begin();
// 處理Blur相關的邏輯
var shouldFire = shouldFireAfterActiveInstanceBlur;
shouldFireAfterActiveInstanceBlur = false;
focusedInstanceHandle = null;
return shouldFire;
}
function commitBeforeMutationEffects_begin() {
while (nextEffect !== null) {
var fiber = nextEffect;
var child = fiber.child;
if ((fiber.subtreeFlags & BeforeMutationMask) !== NoFlags && child !== null) {
child.return = fiber;
nextEffect = child;
} else {
commitBeforeMutationEffects_complete();
}
}
}
而commitBeforeMutationEffects_complete同樣是做了一次遍歷,這次的程序則是不斷向上回傳,呼叫程序中不斷執行commitBeforeMutationEffectsOnFiber,
function commitBeforeMutationEffects_complete() {
while (nextEffect !== null) {
var fiber = nextEffect;
setCurrentFiber(fiber);
try {
commitBeforeMutationEffectsOnFiber(fiber);
} catch (error) {
captureCommitPhaseError(fiber, fiber.return, error);
}
resetCurrentFiber();
var sibling = fiber.sibling;
if (sibling !== null) {
// 注意這里,發現了嘛,和completeWork非常相似的邏輯對吧
sibling.return = fiber.return;
nextEffect = sibling;
return;
}
nextEffect = fiber.return;
}
}
繼續到commitBeforeMutationEffectsOnFiber,發現這里只有兩個簡單的內容
- 一個是對于ClassComponent會呼叫getSnapshotBeforeUpdate
- 另一個則是會HostRoot進行
clearContainer(root.containerInfo)
# 小結
那么我們對BeforeMutation階段進行小結,現在我們知道React在BeforeMutation主要做了兩件事
- 處理真實DOM增刪后的
focus、blur邏輯 - 呼叫ClassComponent的
getSnapshotBeforeUpdate生命周期鉤子
4.4.3 Mutation
commit第二階段,我們會進入commitMutationEffects->commitMutationEffectsOnFiber
if (subtreeHasEffects || rootHasEffect) {
...
commitMutationEffects(root, finishedWork, lanes);
...
} else {
// No effects.
root.current = finishedWork;
}
commitMutationEffectsOnFiber是一個368行的函式,它會根據Fiber.tag和Fiber.flags走不同的Mutation邏輯
目前來說,除了ScopeComponent外的所有Component型別都會執行
recursivelyTraverseMutationEffects(root, finishedWork);
commitReconciliationEffects(finishedWork);
所以我們首先走入recursivelyTraverseMutationEffects,可以看到recursivelyTraverseMutationEffects主要分成兩部分,
上邊的部分負責從Fiber.deletions中取出具體的deletions執行commitDeletionEffects,后邊則是向下遍歷節點遞回執行commitMutationEffectsOnFiber,
function recursivelyTraverseMutationEffects(root, parentFiber, lanes) {
// Deletions effects can be scheduled on any fiber type. They need to happen
// before the children effects hae fired.
var deletions = parentFiber.deletions;
if (deletions !== null) {
for (var i = 0; i < deletions.length; i++) {
var childToDelete = deletions[i];
try {
commitDeletionEffects(root, parentFiber, childToDelete);
} catch (error) {
captureCommitPhaseError(childToDelete, parentFiber, error);
}
}
}
var prevDebugFiber = getCurrentFiber();
if (parentFiber.subtreeFlags & MutationMask) {
var child = parentFiber.child;
while (child !== null) {
setCurrentFiber(child);
commitMutationEffectsOnFiber(child, root);
child = child.sibling;
}
}
setCurrentFiber(prevDebugFiber);
}
我通覽這部分涉及的flags,發現會執行以下內容:
- Update->Insertion:執行React18推出的新hook,
useInsertionEffect,會包含destory和create兩個階段
-
Update->Layout:執行
useLayoutEffect上一次執行殘留的destory函式 -
Placement:
-
Deletions:洗掉節點
-
Update,more
-
Hydrating :SSR相關,由于博主目前為止沒有實踐過SSR,所以不說,
-
Ref:safelyDetachRef
-
ContentReset
-
Visibility
...
打住,有點多了!我們只關注Update,Deletions,Placement,并且只關注HostComponent
#1 Update
關于FunctionComponent的Update,做的事情其實就在上方前亮點
而對于HostComponent,react 會執行這些內容:
這里最核心的就是commitUpdate,React會通過updateProperties將DOM屬性更新到真實節點上
function commitUpdate(domElement, updatePayload, type, oldProps, newProps, internalInstanceHandle) {
// Apply the diff to the DOM node.
updateProperties(domElement, updatePayload, type, oldProps, newProps); // Update the props handle so that we know which props are the ones with
// with current event handlers.
updateFiberProps(domElement, newProps);
}
(我們其實遇到過類似的函式??)
react還會把這個屬性也更新上去,在我這篇文章中有這個屬性的應用
![]()
#2 Placement
我們只說HostComponent的邏輯,只有真實節點會走到這里,另外兩個tagHostRoot,HostPortal,相比HostComponent只是缺少了ContextReset的內容,
(如果其他型別的tag走到commitPlacement是會報錯的)
那么這里其實主要就是三步:
-
獲取Fiber節點存在HostFiber的父節點,并最侄訓得真實DOM
-
獲取Fiber節點的兄弟真實DOM節點
-
insertOrAppendPlacementNodeIntoContainer,將節點插入或添加到父容器中
走Placement完畢,可以很明顯看到頁面渲染
(appendChildToContainer函式涉及真實DOM的插入/添加操作)
#3 Deletion
deletions是在beginWork的diff程序中獲得的
- 呼叫被洗掉節點的
componentWillUnmount生命周期鉤子,從頁面移除Fiber節點對應DOM節點
- 安全解綁ref
4.4.4 Layout
進入layout階段,證明DOM節點已經渲染完畢了
//將current指向已經完成的workInProgress
root.current = finishedWork;
commitLayoutEffects(finishedWork, root, lanes);
function commitLayoutEffects(finishedWork, root, committedLanes) {
inProgressLanes = committedLanes;
inProgressRoot = root;
var current = finishedWork.alternate;
commitLayoutEffectOnFiber(root, current, finishedWork, committedLanes);
inProgressLanes = null;
inProgressRoot = null;
}
commitLayoutEffects->commitLayoutEffectOnFiber會按照我們熟悉的流程做遞回
(commitLayoutEffectOnFiber和recursivelyTraverseLayoutEffects遞回呼叫)
我們需要關注的是commitLayoutEffectOnFiber中的內容
function commitLayoutEffectOnFiber(finishedRoot, current, finishedWork, committedLanes) {
// When updating this function, also update reappearLayoutEffects, which does
// most of the same things when an offscreen tree goes from hidden -> visible.
var flags = finishedWork.flags;
switch (finishedWork.tag) {
case FunctionComponent:
case ForwardRef:
case SimpleMemoComponent:
{
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork, committedLanes);
//調度useLayoutEffect的create
if (flags & Update) {
commitHookLayoutEffects(finishedWork, Layout | HasEffect);
}
break;
}
case ClassComponent:
{
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork, committedLanes);
//調度componentDidUpdate、componentDidMount等class組件的生命周期鉤子
if (flags & Update) {
commitClassLayoutLifecycles(finishedWork, current);
}
if (flags & Callback) {
commitClassCallbacks(finishedWork);
}
//用真實DOM更新ref
if (flags & Ref) {
safelyAttachRef(finishedWork, finishedWork.return);
}
break;
}
...
case HostComponent:
{
recursivelyTraverseLayoutEffects(finishedRoot, finishedWork, committedLanes);
// 這里會調度組件的docus、img的src標簽
if (current === null && flags & Update) {
commitHostComponentMount(finishedWork);
}
//用真實DOM更新ref
if (flags & Ref) {
safelyAttachRef(finishedWork, finishedWork.return);
}
break;
}
...
}
}
此時React會做一些收尾的作業,正如我在給文章收尾一樣,內容是比較少(水)的,
-
調度
useLayoutEffect的開始階段 -
調度componentDidUpdate、componentDidMount等class組件的生命周期鉤子
-
真實dom上的focus處理、img標簽的src處理
-
AttachRef,獲取真實DOM,更新
ref
更多內容其實都非常好理解,我推薦直接動手看,
4.4.5 After commit end
當然,在layout階段結束后仍有一些收尾作業,
var rootDidHavePassiveEffects = rootDoesHavePassiveEffects;
//上邊執行useEffect時會標記rootDoesHavePassiveEffects=true
//這里會對相關內容進行清除
if (rootDoesHavePassiveEffects) {
rootDoesHavePassiveEffects = false;
rootWithPendingPassiveEffects = root;
pendingPassiveEffectsLanes = lanes;
} else {
releaseRootPooledCache(root, remainingLanes);
}
...
//和react-refresh-runtime相關的模塊
onCommitRoot(finishedWork.stateNode, renderPriorityLevel);
...
// 確保root有一個新的調度,我想找機會試試把這句話注釋
ensureRootIsScheduled(root, now());
// 一些錯誤處理
if (recoverableErrors !== null) {
var onRecoverableError = root.onRecoverableError;
for (var i = 0; i < recoverableErrors.length; i++) {
var recoverableError = recoverableErrors[i];
var componentStack = recoverableError.stack;
var digest = recoverableError.digest;
onRecoverableError(recoverableError.value, {
componentStack: componentStack,
digest: digest
});
}
}
if (hasUncaughtError) {
hasUncaughtError = false;
var error$1 = firstUncaughtError;
firstUncaughtError = null;
throw error$1;
}
// React注釋:請再次閱讀,因為被動效果可能會更新它
if (includesSomeLane(pendingPassiveEffectsLanes, SyncLane) && root.tag !== LegacyRoot) {
flushPassiveEffects();
}
// 無限重渲染的計數
remainingLanes = root.pendingLanes;
if (includesSomeLane(remainingLanes, SyncLane)) {
if (root === rootWithNestedUpdates) {
nestedUpdateCount++;
} else {
nestedUpdateCount = 0;
rootWithNestedUpdates = root;
}
} else {
nestedUpdateCount = 0;
} // If layout work was scheduled, flush it now.
// 執行一些同步任務,這樣無需等待在下一次回圈的時候進行,這里可以參考ensureRootIsScheduled
flushSyncCallbacks();
return null;
那么至此,commit階段算已經完成了,
但是React的渲染卻不能算完成,正如我一開始讀原始碼的初衷是為了知道,我在useEffect里呼叫了更新,這個執行時機和觸發渲染原理是什么情況,
到了這里我會明白,由于我們上述的各種effect、生命周期狗子,此時完全可能再次觸發更新,
而react也會很自然地走進一個新的render+commit的程序,先將觸發更新的內容更新后再繼續原本未更新的,
對于React來講,會在flushWork執行完畢后才真正進入空閑,但是這就是后話了
(flushWork函式)
5 總結
不管在面試還是在生活中,都曾有人問我為什么要看React原始碼,
我剛開始是因為對于hook的架構感興趣而去看的,而現在隨著閱讀逐漸深入,我發現閱讀react原始碼一方面給了我比較強的成就感,這也是我可以堅持下來的原因,另一方面,我們真的會在閱讀中體會到某些思想上的高明,
比如,二進制flags、useEffect形成的環形更新鏈條
閱完本文,期待你對React18的Fiber架構有了更新的認識,也理解了React狀態更新的全流程,更期望你可以將學到的東西真實應用在自己的生活、作業中,我認為這才是讀原始碼最重要的,
那么這里留幾個關于React的問題,默想3分鐘,把識訓沉淀在腦海中,
- 總結一下beginWork和completeWork的作業內容
- useLayoutEffect在什么時機執行
- react是在什么時候、怎么存盤、怎么應用操作依據的?
6 尾聲
Hi~你好,再次認識一下,我是心鎖,致力于前端開發的軟體開發工程師,
這是我第一篇單字符數破5w,字數破1w的文章,耗時一個月零四天,
所以非常期待你的點贊、收藏、分析~
后續呢,我會進行必要的切割,分多文方便閱讀,同時補充更多細節,所以非常期待你的關注,
- https://github.com/GrinZero 這是我的github,我會在上邊更新腦子里突然蹦出來的主意,歡迎你的follow,后續也會把react解讀更新上去,
![]()
(部分專案成果集合圖)
- https://juejin.cn/user/1645288319627576/posts 這是我的掘金個人主頁,期待你的關注,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/509146.html
標籤:其他
下一篇:CSS Flexbox 布局
