react源碼解析6.legacy模式和concurrent模式
視頻課程(高效學習):進入課程
課程目錄:
react啟動的模式
react有3種模式進入主體函數的入口,我們可以從 react官方文檔 使用 Concurrent 模式(實驗性)中對比三種模式:
- legacy 模式:
ReactDOM.render(<App />, rootNode)
。這是當前 React app 使用的方式。當前沒有計划刪除本模式,但是這個模式可能不支持這些新功能。 - blocking 模式:
ReactDOM.createBlockingRoot(rootNode).render(<App />)
。目前正在實驗中。作為遷移到 concurrent 模式的第一個步驟。 - concurrent 模式:
ReactDOM.createRoot(rootNode).render(<App />)
。目前在實驗中,未來穩定之后,打算作為 React 的默認開發模式。這個模式開啟了所有的新功能。
特性對比:
legacy 模式在合成事件中有自動批處理的功能,但僅限於一個瀏覽器任務。非 React 事件想使用這個功能必須使用 unstable_batchedUpdates
。在 blocking 模式和 concurrent 模式下,所有的 setState
在默認情況下都是批處理的。會在開發中發出警告
不同模式在react運行時的含義
legacy模式是我們常用的,它構建dom的過程是同步的,所以在render的reconciler中,如果diff的過程特別耗時,那么導致的結果就是js一直阻塞高優先級的任務(例如用戶的點擊事件),表現為頁面的卡頓,無法響應。
concurrent Mode是react未來的模式,它用時間片調度實現了異步可中斷的任務,根據設備性能的不同,時間片的長度也不一樣,在每個時間片中,如果任務到了過期時間,就會主動讓出線程給高優先級的任務。這部分將在第15節 scheduler&lane模型 。
兩種模式函數主要執行過程
1.主要執行流程:
2.詳細函數調用過程:
用demo_0跟着視頻調試更加清晰,黃色部分是主要任務是創建fiberRootNode和rootFiber,紅色部分是創建Update,藍色部分是調度render階段的入口函數
3.legacy模式:
-
render調用legacyRenderSubtreeIntoContainer,最后createRootImpl會調用到createFiberRoot創建fiberRootNode,然后調用createHostRootFiber創建rootFiber,其中fiberRootNode是整個項目的的根節點,rootFiber是當前應用掛在的節點,也就是ReactDOM.render調用后的根節點
//最上層的節點是整個項目的根節點fiberRootNode ReactDOM.render(<App />, document.getElementById("root"));//rootFiber ReactDOM.render(<App />, document.getElementById("root"));//rootFiber
-
創建完Fiber節點后,legacyRenderSubtreeIntoContainer調用updateContainer創建創建Update對象掛載到updateQueue的環形鏈表上,然后執行scheduleUpdateOnFiber調用performSyncWorkOnRoot進入render階段和commit階段
4.concurrent模式:
- createRoot調用createRootImpl創建fiberRootNode和rootNode
- 創建完Fiber節點后,調用ReactDOMRoot.prototype.render執行updateContainer,然后scheduleUpdateOnFiber異步調度performConcurrentWorkOnRoot進入render階段和commit階段
5.legacy模式主要函數注釋
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
//...
var root = container._reactRootContainer;
var fiberRoot;
if (!root) {
// mount時
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);//創建root節點
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {//處理回調
var originalCallback = callback;
callback = function () {
var instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
}
unbatchedUpdates(function () {
updateContainer(children, fiberRoot, parentComponent, callback);//創建update入口
});
} else {
// update時
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {//處理回調
var _originalCallback = callback;
callback = function () {
var instance = getPublicRootInstance(fiberRoot);
_originalCallback.call(instance);
};
}
updateContainer(children, fiberRoot, parentComponent, callback);
}
}
function createFiberRoot(containerInfo, tag, hydrate, hydrationCallbacks) {
var root = new FiberRootNode(containerInfo, tag, hydrate);//創建fiberRootNode
const uninitializedFiber = createHostRootFiber(tag);//創建rootFiber
//rootFiber和fiberRootNode連接
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
//創建updateQueue
initializeUpdateQueue(uninitializedFiber);
return root;
}
//對於HostRoot或者ClassComponent會使用initializeUpdateQueue創建updateQueue,然后將updateQueue掛載到fiber節點上
export function initializeUpdateQueue<State>(fiber: Fiber): void {
const queue: UpdateQueue<State> = {
baseState: fiber.memoizedState,//初始state,后面會基於這個state,根據Update計算新的state
firstBaseUpdate: null,//Update形成的鏈表的頭
lastBaseUpdate: null,//Update形成的鏈表的尾
//新產生的update會以單向環狀鏈表保存在shared.pending上,計算state的時候會剪開這個環狀鏈表,並且連接在 //lastBaseUpdate后
shared: {
pending: null,
},
effects: null,
};
fiber.updateQueue = queue;
}
function updateContainer(element, container, parentComponent, callback) {
var lane = requestUpdateLane(current$1);//獲取當前可用lane 在12章講解
var update = createUpdate(eventTime, lane); //創建update
update.payload = {
element: element//jsx
};
enqueueUpdate(current$1, update);//update入隊
scheduleUpdateOnFiber(current$1, lane, eventTime);//調度update
return lane;
}
function scheduleUpdateOnFiber(fiber, lane, eventTime) {
if (lane === SyncLane) {//同步lane 對應legacy模式
//...
performSyncWorkOnRoot(root);//render階段的起點 render在第6章講解
} else {//concurrent模式
//...
ensureRootIsScheduled(root, eventTime);//確保root被調度
}
}
6.concurrent主要函數注釋:
function ensureRootIsScheduled(root, currentTime) {
//...
var nextLanes = getNextLanes(root, root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes); //計算nextLanes
//...
//將lane的優先級轉換成schduler的優先級
var schedulerPriorityLevel = lanePriorityToSchedulerPriority(newCallbackPriority);
//以schedulerPriorityLevel的優先級執行performConcurrentWorkOnRoot 也就是concurrent模式的起點
newCallbackNode = scheduleCallback(schedulerPriorityLevel,performConcurrentWorkOnRoot.bind(null, root));
}
7.兩種模式的不同點:
- createRootImpl中傳入的第二個參數不一樣 一個是LegacyRoot一個是ConcurrentRoot
- requestUpdateLane中獲取的lane的優先級不同
- 在函數scheduleUpdateOnFiber中根據不同優先級進入不同分支,legacy模式進入performSyncWorkOnRoot,concurrent模式會異步調度performConcurrentWorkOnRoot