React 16版本已經推出多時,提出了包括Portal,異常邊界等新特性,最重要的是重寫了調和算法,推出了新版本算法實現-Fiber,因而博主歷時三週,在業餘時間學習Fiber架構實現和源碼,對Fiber總體有了初步瞭解,並總結分享出來,若對一些源碼不感興趣,大可跳過,另博主水平有限,如有不對之處,歡迎指正。html
歡迎訪問本人博客node
React的定位是一個構建用戶界面的JavaScript類庫,它使用JavaScript語言開發UI組件,可使用多種方式渲染這些組件,輸出用戶界面,較大程度的達到了跨技術棧跨平臺的兼容重用:react
We don’t make assumptions about the rest of your technology stack, so you can develop new features in React without rewriting existing code.git
如今的React已然在如下幾個方面發揮的都很不錯:github
在這些不一樣場景,渲染的主體很明顯是不同的,有諸如web應用的DOM渲染,React Native的原生View渲染,服務端字符串渲染等,要作到兼容適應多種不一樣渲染環境,很顯然,React不能侷限固定渲染UI的方式。web
React核心內容也確實只包括定義組件相關的內容和API,源碼能夠查看,實際項目中,能夠看到首先須要使用以下代碼:算法
import React from 'react';
複製代碼
這句代碼作的就是引入了React核心源碼模塊。react-native
上一節已經說到React核心內容只涉及如何定義組件,並不涉及具體的組件渲染(即輸出用戶界面),這須要額外引入渲染模塊,以渲染React定義的組件:數組
React DOM渲染模塊:將React組件渲染爲DOM,而後能夠被瀏覽器處理呈現給用戶,這就是一般在web應用中引入的react-dom
模塊:瀏覽器
import React from 'react';
import { render } from 'react-dom';
import App from './apps/App.js';
render(
<App />,
document.getElementById('mainBox')
);
複製代碼
如上代碼,App
是使用React核心模塊定義的組件,而後使用react-dom
渲染模塊提供的render
方法將其渲染爲DOM輸出至頁面。
React Native 渲染:將React組件渲染爲移動端原生View,在React Native應用中引入react-native
模塊,它提供相應渲染方法能夠渲染React組件:
import { AppRegistry } from 'react-native';
import App from './src/app.js';
AppRegistry.registerComponent('fuc', () => App);
複製代碼
如上,App
是React根組件,使用react-native
渲染器的AppRegistry.registerComponent
方法將其渲染爲原生View。
React測試渲染:將React組件渲染爲JSON樹,用來完成Jest的快照測試,內容在react-test-renderer
模塊:
import ReactTestRenderer from 'react-test-renderer';
const renderer = ReactTestRenderer.create(
<Link page="https://www.facebook.com/">Facebook</Link>
);
console.log(renderer.toJSON());
// { type: 'a',
// props: { href: 'https://www.facebook.com/' },
// children: [ 'Facebook' ] }
複製代碼
React矢量圖渲染:將React組件渲染爲對應的適量圖(ART庫);
web React應用是最多見的,也是最易於理解的,因此本篇後文均從React-DOM渲染器角度解析Fiber。
如前面兩節所述,React核心是定義組件,渲染組件方式由環境決定,定義組件,組件狀態管理,生命週期方法管理,組件更新等應該跨平臺一致處理,不受渲染環境影響,這部份內容統一由調和器(Reconciler)處理,源碼傳送,不一樣渲染器都會使用該模塊。調和器主要做用就是在組件狀態變動時,調用組件樹各組件的render
方法,渲染,卸載組件。
咱們知道瀏覽器渲染引擎是單線程的,在React 15.x版本及以前版本,計算組件樹變動時將會阻塞整個線程,整個渲染過程是連續不中斷完成的,而這時的其餘任務都會被阻塞,如動畫等,這可能會使用戶感受到明顯卡頓,好比當你在訪問某一網站時,輸入某個搜索關鍵字,更優先的應該是交互反饋或動畫效果,若是交互反饋延遲200ms,用戶則會感受較明顯的卡頓,而數據響應晚200毫秒並沒太大問題。這個版本的調和器能夠稱爲棧調和器(Stack Reconciler),其調和算法大體過程見React Diff算法 和React Stack Reconciler實現。
Stack Reconcilier的主要缺陷就是不能暫停渲染任務,也不能切分任務,沒法有效平衡組件更新渲染與動畫相關任務間的執行順序,即不能劃分任務優先級,有可能致使重要任務卡頓,動畫掉幀等問題。
React 16版本提出了一個更先進的調和器,它容許渲染進程分段完成,而沒必要須一次性完成,中間能夠返回至主進程控制執行其餘任務。而這是經過計算部分組件樹的變動,並暫停渲染更新,詢問主進程是否有更高需求的繪製或者更新任務須要執行,這些高需求的任務完成後纔開始渲染。這一切的實現是在代碼層引入了一個新的數據結構-Fiber對象,每個組件實例對應有一個fiber實例,此fiber實例負責管理組件實例的更新,渲染任務及與其餘fiber實例的聯繫。
這個新推出的調和器就叫作纖維調和器(Fiber Reconciler),它提供的新功能主要有:
render
方法能夠返回多元素(便可以返回數組);說了這麼多,終於要正式出場本篇主角:Fiber了,React最新版本已經升到16.1.1,估計16.x穩定版不會太遠,讓咱們先睹爲快吧。
前面說到Fiber能夠異步實現不一樣優先級任務的協調執行,那麼對於DOM渲染器而言,在JavaScript層是否提供這種方式呢,仍是說只能使用setTimeout模擬呢?目前新版本主流瀏覽器已經提供了可用API:requestIdleCallback
和requestAnimationFrame
:
一般,客戶端線程執行任務時會以幀的形式劃分,大部分設備控制在30-60幀是不會影響用戶體驗;在兩個執行幀之間,主線程一般會有一小段空閒時間,requestIdleCallback
能夠在這個空閒期(Idle Period)調用空閒期回調(Idle Callback),執行一些任務。
Fiber所作的就是須要分解渲染任務,而後根據優先級使用API調度,異步執行指定任務:
requestIdleCallback
處理;requestAnimationFrame
處理;requestIdleCallback
能夠在多個空閒期調用空閒期回調,執行任務;requestIdleCallback
方法提供deadline,即任務執行限制時間,以切分任務,避免長時間執行,阻塞UI渲染而致使掉幀;具體執行任務實現源碼傳送:
若支持原生API,具體原生實現見上文給出的連接:
rIC = window.requestIdleCallback;
cIC = window.cancelIdleCallback;
export {now, rIC, cIC};
複製代碼
若不支持,則自定義實現:
let isIdleScheduled = false; // 是否在執行空閒期回調
let frameDeadlineObject = {
didTimeout: false,
timeRemaining() {
// now = Performance.now || Date.now
const remaining = frameDeadline - now();
// 計算獲得當前幀運行剩餘時間
return remaining > 0 ? remaining : 0;
},
};
// 幀回調
const animationTick = function(rafTime) {
...
if (!isIdleScheduled) {
// 不在執行空閒期回調,代表能夠調用空閒期回調
isIdleScheduled = true;
// 執行Idle空閒期回調
idleTick();
}
};
// 空閒期回調
const idleTick = function() {
// 重置爲false,代表能夠調用空閒期回調
isIdleScheduled = false;
const currentTime = now();
if (frameDeadline - currentTime <= 0) {
// 幀到期時間小於當前時間,說明已過時
if (timeoutTime !== -1 && timeoutTime <= currentTime) {
// 此幀已過時,且發生任務處理函數(執行具體任務,傳入的回調)的超時
// 須要執行任務處理,下文將調用;
frameDeadlineObject.didTimeout = true;
} else {
// 幀已過時,但沒有發生任務處理函數的超時,暫時不調用任務處理函數
if (!isAnimationFrameScheduled) {
// 當前沒有調度別的幀回調函數
// 調度下一幀
isAnimationFrameScheduled = true;
requestAnimationFrame(animationTick);
}
// Exit without invoking the callback.
return;
}
} else {
// 這一幀還有剩餘時間
// 標記未超時,以後調用任務處理函數
frameDeadlineObject.didTimeout = false;
}
// 緩存的任務處理函數
timeoutTime = -1;
const callback = scheduledRICCallback;
scheduledRICCallback = null;
if (callback !== null) {
// 執行回調
callback(frameDeadlineObject);
}
}
// 自定義模擬requestIdleCallback
rIC = function(
callback: (deadline: Deadline) => void, // 傳入的任務處理函數參數
options?: {timeout: number} // 其餘參數
) {
// 回調函數
scheduledRICCallback = callback;
if (options != null && typeof options.timeout === 'number') {
// 計算過時時間
timeoutTime = now() + options.timeout;
}
if (!isAnimationFrameScheduled) {
// 當前沒有調度別的幀回調函數
isAnimationFrameScheduled = true;
// 初始開始執行幀回調
requestAnimationFrame(animationTick);
}
return 0;
};
複製代碼
frameDeadline
:是以啓發法,從30fps(即30幀)開始調整獲得的更適於當前環境的一幀限制時間;timeRemaining
:計算requestIdleCallback
這次空閒(幀)執行任務剩餘時間,即距離deadline的時間;options.timeout
:Fiber內部調用rIC
API執行異步任務時,傳遞的任務到期時間參數;frameDeadlineObject
:計算獲得的某一幀可用時間對象,兩個屬性分別表示:
frameDeadlineObject
對象是基於傳入的timeout
參數和此模塊內部自調整獲得的frameDeadline
參數計算得出;咱們已經知道了Fiber的功能及其主要特色,那麼其如何和組件聯繫,而且如何實現效果的呢,如下幾點能夠歸納:
注意Fiber與fiber的區別,Fiber是指調和器算法,fiber則是調和器算法組成單元,和組件與應用關係相似,每個組件實例會有對應的fiber實例負責該組件的調和。
截止目前,咱們對Fiber應該有了初步的瞭解,在具體介紹Fiber的實現與架構以前,準備先簡單介紹一下Fiber的數據結構,數據結構能必定程度反映其總體工做架構。
其實,一個fiber就是一個JavaScript對象,以鍵值對形式存儲了一個關聯組件的信息,包括組件接收的props,維護的state,最後須要渲染出的內容等。接下來咱們將介Fiber對象的主要屬性。
首先Fiber對象的定義以下:
// 一個Fiber對象做用於一個組件
export type Fiber = {|
// 標記fiber類型tag.
tag: TypeOfWork,
// fiber對應的function/class/module類型組件名.
type: any,
// fiber所在組件樹的根組件FiberRoot對象
stateNode: any,
// 處理完當前fiber後返回的fiber,
// 返回當前fiber所在fiber樹的父級fiber實例
return: Fiber | null,
// fiber樹結構相關連接
child: Fiber | null,
sibling: Fiber | null,
index: number,
// 當前處理過程當中的組件props對象
pendingProps: any,
// 緩存的以前組件props對象
memoizedProps: any, // The props used to create the output.
// The state used to create the output
memoizedState: any,
// 組件狀態更新及對應回調函數的存儲隊列
updateQueue: UpdateQueue<any> | null,
// 描述當前fiber實例及其子fiber樹的數位,
// 如,AsyncUpdates特殊字表示默認以異步形式處理子樹,
// 一個fiber實例建立時,此屬性繼承自父級fiber,在建立時也能夠修改值,
// 但隨後將不可修改。
internalContextTag: TypeOfInternalContext,
// 更新任務的最晚執行時間
expirationTime: ExpirationTime,
// fiber的版本池,即記錄fiber更新過程,便於恢復
alternate: Fiber | null,
// Conceptual aliases
// workInProgress : Fiber -> alternate The alternate used for reuse happens
// to be the same as work in progress.
|};
複製代碼
能夠理解爲一個fiber版本池,用於交替記錄組件更新(切分任務後變成多階段更新)過程當中fiber的更新,由於在組件更新的各階段,更新前及更新過程當中fiber狀態並不一致,在須要恢復時(如,發生衝突),便可使用另外一者直接回退至上一版本fiber。
- 使用alternate屬性雙向鏈接一個當前fiber和其work-in-progress,當前fiber實例的alternate屬性指向其work-in-progress,work-in-progress的alternate屬性指向當前穩定fiber;
- 當前fiber的替換版本是其work-in-progress,work-in-progress的交替版本是當前fiber;
- 當work-in-progress更新一次後,將同步至當前fiber,而後繼續處理,同步直至任務完成;
- work-in-progress指向處理過程當中的fiber,而當前fiber老是維護處理完成的最新版本的fiber。
建立fiber實例即返回一個帶有上一小節描述的諸多屬性的JavaScript對象,FiberNode
即根據傳入的參數構造返回一個初始化的對象:
var createFiber = function(
tag: TypeOfWork,
key: null | string,
internalContextTag: TypeOfInternalContext,
) {
return new FiberNode(tag, key, internalContextTag);
};
複製代碼
建立alternate fiber以處理任務的實現以下:
// 建立一個alternate fiber處理任務
export function createWorkInProgress(
current: Fiber,
pendingProps: any,
expirationTime: ExpirationTime,
) {
let workInProgress = current.alternate;
if (workInProgress === null) {
workInProgress = createFiber(
current.tag,
current.key,
current.internalContextTag,
);
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
// 造成alternate關係,互相交替模擬版本池
workInProgress.alternate = current;
current.alternate = workInProgress;
}
workInProgress.expirationTime = expirationTime;
workInProgress.pendingProps = pendingProps;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
...
return workInProgress;
}
複製代碼
上一小節,Fiber對象中有個tag
屬性,標記fiber類型,而fiber實例是和組件對應的,因此其類型基本上對應於組件類型,源碼見ReactTypeOfWork模塊:
export type TypeOfWork = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10;
export const IndeterminateComponent = 0; // 尚不知是類組件仍是函數式組件
export const FunctionalComponent = 1; // 函數式組件
export const ClassComponent = 2; // Class類組件
export const HostRoot = 3; // 組件樹根組件,能夠嵌套
export const HostPortal = 4; // 子樹. Could be an entry point to a different renderer.
export const HostComponent = 5; // 標準組件,如地div, span等
export const HostText = 6; // 文本
export const CallComponent = 7; // 組件調用
export const CallHandlerPhase = 8; // 調用組件方法
export const ReturnComponent = 9; // placeholder(佔位符)
export const Fragment = 10; // 片斷
複製代碼
在調度執行任務的時候會根據不一樣類型fiber,即fiber.tag值進行不一樣處理。
FiberRoot
對象,主要用來管理組件樹組件的更新進程,同時記錄組件樹掛載的DOM容器相關信息,具體定義見ReactFiberRoot模塊:
export type FiberRoot = {
// fiber節點的容器元素相關信息,一般會直接傳入容器元素
containerInfo: any,
// 當前fiber樹中激活狀態(正在處理)的fiber節點,
current: Fiber,
// 此節點剩餘的任務到期時間
remainingExpirationTime: ExpirationTime,
// 更新是否能夠提交
isReadyForCommit: boolean,
// 準備好提交的已處理完成的work-in-progress
finishedWork: Fiber | null,
// 多組件樹FirberRoot對象以單鏈表存儲連接,指向下一個須要調度的FiberRoot
nextScheduledRoot: FiberRoot | null,
};
複製代碼
import {
ClassComponent,
HostRoot
} from 'shared/ReactTypeOfWork';
// 建立返回一個初始根組件對應的fiber實例
function createHostRootFiber(): Fiber {
// 建立fiber
const fiber = createFiber(HostRoot, null, NoContext);
return fiber;
}
export function createFiberRoot(
containerInfo: any,
hydrate: boolean,
) {
// 建立初始根組件對應的fiber實例
const uninitializedFiber = createHostRootFiber();
// 組件樹根組件的FiberRoot對象
const root = {
// 根組件對應的fiber實例
current: uninitializedFiber,
containerInfo: containerInfo,
pendingChildren: null,
remainingExpirationTime: NoWork,
isReadyForCommit: false,
finishedWork: null,
context: null,
pendingContext: null,
hydrate,
nextScheduledRoot: null,
};
// 組件樹根組件fiber實例的stateNode指向FiberRoot對象
uninitializedFiber.stateNode = root;
return root;
}
複製代碼
在生成組件樹的FiberRoot對象後,會爲子組件生成各自的fiber實例,這一部分由ReactChildFiber模塊實現:
// 調和(處理更新)子fibers
export const reconcileChildFibers = ChildReconciler(true);
// 掛載(初始化)子fibers
export const mountChildFibers = ChildReconciler(false);
複製代碼
而ChildReconciler
方法所作的則是根據傳入參數判斷是調用初始化子組件fibers邏輯仍是執行調和已有子組件fibers邏輯。
ChildReconciler
方法,返回reconcileChildFibers
方法:
props.children
時,其類型能夠是對象或數組,字符串,是數字等;function ChildReconciler(a) {
function reconcileChildFibers(
returnFiber: Fiber, currentFirstChild: Fiber | null,
newChild: any, expirationTime: ExpirationTime,
) {
// Handle object types
const isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {
// 子組件實例類型,以Symbol符號表示的
switch (newChild.$$typeof) {
// React Element
case REACT_ELEMENT_TYPE:
return placeSingleChild(
reconcileSingleElement(
returnFiber, currentFirstChild,
newChild, expirationTime
)
);
// React組件調用
case REACT_CALL_TYPE:
return placeSingleChild(reconcileSingleCall(...));
// placeholder
case REACT_RETURN_TYPE:
return ...;
case REACT_PORTAL_TYPE:
return ...;
}
}
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(reconcileSingleTextNode(...));
}
if (isArray(newChild)) {
return reconcileChildrenArray(...);
}
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(...);
}
...
}
}
複製代碼
在學習Fiber的時候,我嘗試去閱讀源碼,發現經過這種方式很難快速理解,學習Fiber,而先了解調和器是幹什麼的及調和器在React中的存在形式,而後再學習Fiber的結構及算法實現思路,明白從組件被定義到渲染至頁面它須要作什麼,這也是本篇文章的組織形式。
咱們已經知道Fiber能夠切分任務並設置不一樣優先級,那麼是如何實現劃分優先級的呢,其表現形式什麼呢?
Fiber切分任務並調用requestIdleCallback
和requestAnimationFrame
API,保證渲染任務和其餘任務,在不影響應用交互,不掉幀的前提下,穩定執行,而實現調度的方式正是給每個fiber實例設置到期執行時間,不一樣時間即表明不一樣優先級,到期時間越短,則表明優先級越高,須要儘早執行。
所謂的到期時間(ExpirationTime),是相對於調度器初始調用的起始時間而言的一個時間段;調度器初始調用後的某一段時間內,須要調度完成這項更新,這個時間段長度值就是到期時間值。
Fiber提供ReactFiberExpirationTime模塊實現到期時間的定義:
export const NoWork = 0; // 沒有任務等待處理
export const Sync = 1; // 同步模式,當即處理任務
export const Never = 2147483647; // Max int32: Math.pow(2, 31) - 1
const UNIT_SIZE = 10; // 過時時間單元(ms)
const MAGIC_NUMBER_OFFSET = 2; // 到期時間偏移量
// 以ExpirationTime特定單位(1單位=10ms)表示的到期執行時間
// 1 unit of expiration time represents 10ms.
export function msToExpirationTime (ms) {
// 老是增長一個偏移量,在ms<10時與Nowork模式進行區別
return ((ms / UNIT_SIZE) | 0) + MAGIC_NUMBER_OFFSET;
}
// 以毫秒錶示的到期執行時間
export function expirationTimeToMs(expirationTime: ExpirationTime) {
return (expirationTime - MAGIC_NUMBER_OFFSET) * UNIT_SIZE;
}
// 向上取整(整數單位到期執行時間)
// precision範圍精度:彌補任務執行時間偏差
function ceiling(num, precision) {
return (((num / precision) | 0) + 1) * precision;
}
// 計算處理偏差時間在內的到期時間
export function computeExpirationBucket(currentTime, expirationInMs, bucketSizeMs,) {
return ceiling(
currentTime + expirationInMs / UNIT_SIZE,
bucketSizeMs / UNIT_SIZE
);
}
複製代碼
該模塊提供的功能主要有:
其實在15.x版本中出現了對於任務的優先層級劃分,ReactPriorityLevel模塊:
export type PriorityLevel = 0 | 1 | 2 | 3 | 4 | 5;
module.exports = {
NoWork: 0, // No work is pending.
SynchronousPriority: 1, // For controlled text inputs. Synchronous side-effects.
AnimationPriority: 2, // Needs to complete before the next frame.
HighPriority: 3, // Interaction that needs to complete pretty soon to feel responsive.
LowPriority: 4, // Data fetching, or result from updating stores.
OffscreenPriority: 5, // Won't be visible but do the work in case it becomes visible.
};
複製代碼
相對於PriorityLevel的簡單層級劃分,在16.x版本中使用的則是ExpirationTime的到期時間方式表示任務的優先級,能夠更好的對任務進行切分,調度。
前面介紹調和器主要做用就是在組件狀態變動時,調用組件樹各組件的render
方法,渲染,卸載組件,而Fiber使得應用能夠更好的協調不一樣任務的執行,調和器內關於高效協調的實現,咱們能夠稱它爲調度器(Scheduler)。
顧名思義,調度器即調度資源以執行指定任務,React應用中應用組件的更新與渲染,須要佔用系統CPU資源,若是不能很好的進行資源平衡,合理調度,優化任務執行策略,那很容易形成CPU這一緊缺資源的消耗和浪費,容易形成頁面卡頓,動畫掉幀,組件更新異常等諸多問題,就像城市交通調度同樣,若是不能有效調度,交通情況極可能將擁堵不堪。
在React 15.x版本中,組件的狀態變動將直接致使其子組件樹的從新渲染,新版本Fiber算法將在調度器方面進行全面改進,主要的關注點是:
export default function () {
...
return {
computeAsyncExpiration,
computeExpirationForFiber,
scheduleWork,
batchedUpdates,
unbatchedUpdates,
flushSync,
deferredUpdates,
};
}
複製代碼
如上調度器主要輸出API爲實現調度任務,拉取更新,延遲更新等功能。
調度器如何切分任務劃分優先級的呢?在React調和算法中,任務由fiber實例描述,因此要劃分任務優先級,等效於設置fiber的到期時間(expirationTime),調度器內提供了computeExpirationForFiber
方法以計算某一個fiber的到期時間:
import {
NoWork, Sync, Never, msToExpirationTime,
expirationTimeToMs, computeExpirationBucket
} from './ReactFiberExpirationTime';
// 表示下一個要處理的任務的到期時間,默認爲NoWork,即當前沒有正在等待執行的任務;
// Nowork默認更新策略:異步模式下,異步執行任務;同步模式下同步執行任務
let expirationContext = NoWork;
// 下一次渲染到期時間
let nextRenderExpirationTime = NoWork;
// 異步更新
export const AsyncUpdates = 1;
// 初始時間(ms).
const startTime = now();
// ExpirationTime單位表示的當前時間(ExpirationTime單位,初始值傳入0)
let mostRecentCurrentTime = msToExpirationTime(0);
// 計算fiber的到期時間
function computeExpirationForFiber(fiber) {
let expirationTime;
if (isWorking) {
if (isCommitting) {
// 在提交階段的更新任務
// 須要明確設置同步優先級(Sync Priority)
expirationTime = Sync;
} else {
// 在渲染階段發生的更新任務
// 須要設置爲下一次渲染時間的到期時間優先級
expirationTime = nextRenderExpirationTime;
}
} else {
// 不在任務執行階段,須要計算新的過時時間
// 明確傳遞useSyncScheduling爲true代表指望同步調用
// 且fiber.internalContextTag != AsyncUpdates
if (useSyncScheduling && !(fiber.internalContextTag & AsyncUpdates)) {
// 同步更新,設置爲同步標記
expirationTime = Sync;
} else {
// 異步更新,計算異步到期時間
expirationTime = computeAsyncExpiration();
}
}
return expirationTime;
}
複製代碼
Sync
,即同步執行模式;useSyncScheduling
且fiber.internalContextTag
值不等於AsyncUpdates
,則代表是同步模式,設置爲Sync
;computeAsyncExpiration
方法從新計算此fiber的到期時間;// 從新計算當前時間(ExpirationTime單位表示)
function recalculateCurrentTime() {
const ms = now() - startTime;
// ExpirationTime單位表示的當前時間
// 時間段值爲 now() - startTime(起始時間)
mostRecentCurrentTime = msToExpirationTime(ms);
return mostRecentCurrentTime;
}
// 計算異步任務的到期時間
function computeAsyncExpiration() {
// 計算獲得ExpirationTime單位的當前時間
// 聚合類似的更新在一塊兒
// 更新應該在 ~1000ms,最多1200ms內完成
const currentTime = recalculateCurrentTime();
// 對於每一個fiber的指望到期時間的增值,最大值爲1000ms
const expirationMs = 1000;
// 到期時間的可接受偏差時間,200ms
const bucketSizeMs = 200;
// 返回包含偏差時間在內的到期時間
return computeExpirationBucket(currentTime, expirationMs, bucketSizeMs);
}
複製代碼
對於每個fiber咱們指望的到期時間參數是1000ms,另外因爲任務執行時間偏差,接受200ms偏差,最後計算獲得的到期時間默認返回值爲ExpirationTime單位。
上一節介紹了調度器主要提供computeExpirationForFiber
等方法支持計算任務優先級(到期時間),接下來介紹調度器如何調度任務。
React應用更新時,Fiber從當前處理節點,層層遍歷至組件樹根組件,而後開始處理更新,調用前面的
requestIdleCallback
等API執行更新處理。
主要調度邏輯實如今scheduleWork
:
fiber.return
屬性,從當前fiber實例層層遍歷至組件樹根組件;requestWork
方法開始處理任務,並傳入獲取的組件樹根組件FiberRoot對象和任務到期時間;// 調度任務
// expirationTime爲指望的任務到期時間
function scheduleWork(fiber, expirationTime: ExpirationTime) {
return scheduleWorkImpl(fiber, expirationTime, false);
}
function scheduleWorkImpl(
fiber, expirationTime
) {
let node = fiber;
while (node !== null) {
// 向上遍歷至根組件fiber實例,並依次更新expirationTime到期時間
if (
node.expirationTime === NoWork ||
node.expirationTime > expirationTime
) {
// 若fiber實例到期時間大於指望的任務到期時間,則更新fiber到期時間
node.expirationTime = expirationTime;
}
// 同時更新alternate fiber的到期時間
if (node.alternate !== null) {
if (
node.alternate.expirationTime === NoWork ||
node.alternate.expirationTime > expirationTime
) {
// 若alternate fiber到期時間大於指望的任務到期時間,則更新fiber到期時間
node.alternate.expirationTime = expirationTime;
}
}
// node.return爲空,說明到達組件樹頂部
if (node.return === null) {
if (node.tag === HostRoot) {
// 確保是組件樹根組件並獲取FiberRoot實例
const root = node.stateNode;
// 請求處理任務
requestWork(root, expirationTime);
} else {
return;
}
}
// 獲取父級組件fiber實例
node = node.return;
}
}
複製代碼
處理任務的requestWork
方法實現以下:
// 當根節點發生更新時,調度器將調用requestWork方法開始任務處理過程
// It's up to the renderer to call renderRoot at some point in the future.
function requestWork(root: FiberRoot, expirationTime) {
const remainingExpirationTime = root.remainingExpirationTime;
if (remainingExpirationTime === NoWork ||
expirationTime < remainingExpirationTime) {
// 若任務剩餘到期時間大於指望的任務到期時間,則須要更新
root.remainingExpirationTime = expirationTime;
}
if (expirationTime === Sync) {
// 同步
performWork(Sync, null);
} else {
// 異步
scheduleCallbackWithExpiration(expirationTime);
}
}
複製代碼
咱們知道若是須要實現組件的異步更新,確定須要在更新前將更新任務進行存儲,而後異步任務開始的時候讀取更新並實現組件更新,存儲更新任務就須要一個數據結構,最多見的就是棧和隊列,Fiber的實現方式就是隊列。
Fiber切分任務爲多個任務單元(Work Unit)後,須要劃分優先級而後存儲在更新隊列,隨後按優先級進行調度執行。咱們知道每個組件都對應有一個fiber實例,fiber實例即負責管理調度組件的任務單元,因此須要爲每個組件fiber實例維護一個更新隊列。
Fiber更新隊列由ReactFiberUpdateQueue模塊實現,主要涉及:
// 一個更新對應的數據結構
export type Update<State> = {
expirationTime: ExpirationTime,
partialState: PartialState<any, any>,
callback: Callback | null,
isReplace: boolean,
isForced: boolean,
next: Update<State> | null,
};
// 更新隊列,以單鏈表形式表示並持久化
// 調度一個更新任務時,將其添加至當前(current)fiber和work-in-progress fiber的更新隊列中;
// 這兩個更新隊列相互獨立但共享同一個持久化數據結構;
// work-in-progress更新隊列一般是current fiber更新隊列的子集;
// 發生調和時,更新任務從work-in-progress fiber更新隊列移除,
// current fiber內的更新任務則保留,當work-in-progress中斷時能夠從current fiber恢復;
// 提交完更新時,work-in-progress fiber就會變成current fiber
export type UpdateQueue<State> = {
// 若存在更早添加至隊列的更新未被處理,
// 則此已處理的更新並不會從隊列中移除-先進先出原則
// 因此須要維護baseState,表明第一個未處理的更新的基礎狀態,
// 一般這就是隊列中的第一個更新,由於在隊列首部的已處理更新會被移除
baseState: State,
// 同理,須要維護最近的未處理的更新的到期時間,
// 即未處理更新中到期時間值最小的
expirationTime: ExpirationTime,
first: Update<State> | null,
last: Update<State> | null,
callbackList: Array<Update<State>> | null,
hasForceUpdate: boolean,
isInitialized: boolean
};
// 添加更新至更新隊列
export function insertUpdateIntoQueue<State>(
queue: UpdateQueue<State>,
update: Update<State>
){
// 添加更新至隊列尾部
if (queue.last === null) {
// 隊列爲空
queue.first = queue.last = update;
} else {
queue.last.next = update;
queue.last = update;
}
if (
queue.expirationTime === NoWork ||
queue.expirationTime > update.expirationTime
) {
// 更新最近到期時間
queue.expirationTime = update.expirationTime;
}
}
// 添加更新至fiber實例
export function insertUpdateIntoFiber<State>(
fiber: Fiber,
update: Update<State>,
) {
// 能夠建立兩個獨立的更新隊列
// alternate主要用來保存更新過程當中各版本更新隊列,方便崩潰或衝突時回退
const alternateFiber = fiber.alternate;
let queue1 = fiber.updateQueue;
if (queue1 === null) {
// 更新隊列不存在,則建立一個空的更新隊列
queue1 = fiber.updateQueue = createUpdateQueue((null));
}
let queue2;
if (alternateFiber !== null) {
// alternate fiber實例存在,則須要爲此
queue2 = alternateFiber.updateQueue;
if (queue2 === null) {
queue2 = alternateFiber.updateQueue = createUpdateQueue((null: any));
}
} else {
queue2 = null;
}
queue2 = queue2 !== queue1 ? queue2 : null;
// 若是隻存在一個更新隊列
if (queue2 === null) {
insertUpdateIntoQueue(queue1, update);
return;
}
// 若是任意更新隊列爲空,則須要將更新添加至兩個更新隊列
if (queue1.last === null || queue2.last === null) {
insertUpdateIntoQueue(queue1, update);
insertUpdateIntoQueue(queue2, update);
return;
}
// 若是2個更新隊列均非空,則添加更新至第一個隊列,並更新另外一個隊列的尾部更新項
insertUpdateIntoQueue(queue1, update);
queue2.last = update;
}
// 處理更新隊列任務,返回新狀態對象
export function processUpdateQueue<State>(
current, workInProgress, queue, instance, props,
renderExpirationTime,
) {
if (current !== null && current.updateQueue === queue) {
// 克隆current fiber以建立work-in-progress fiber
const currentQueue = queue;
queue = workInProgress.updateQueue = {
baseState: currentQueue.baseState,
expirationTime: currentQueue.expirationTime,
first: currentQueue.first,
last: currentQueue.last,
isInitialized: currentQueue.isInitialized,
// These fields are no longer valid because they were already committed. Reset them.
callbackList: null,
hasForceUpdate: false,
};
}
// Reset the remaining expiration time. If we skip over any updates, we'll
// increase this accordingly.
queue.expirationTime = NoWork;
let dontMutatePrevState = true;
let update = queue.first;
let didSkip = false;
while (update !== null) {
const updateExpirationTime = update.expirationTime;
if (updateExpirationTime > renderExpirationTime) {
// 此更新優先級不夠,不處理,跳過
if (queue.expirationTime === NoWork ||
queue.expirationTime > updateExpirationTime
) {
// 從新設置最近未處理更新的到期時間
queue.expirationTime = updateExpirationTime;
}
update = update.next;
continue;
}
// 優先級足夠,處理
let partialState;
if (update.isReplace) {
// 使用replaceState()直接替換狀態對象方式更新時
// 獲取新狀態對象
state = getStateFromUpdate(update, instance, state, props);
// 不須要合併至以前狀態對象,標記爲true
dontMutatePrevState = true;
} else {
// 更新部分狀態方式
// 獲取更新部分狀態時的狀態對象
partialState = getStateFromUpdate(update, instance, state, props);
if (partialState) {
if (dontMutatePrevState) {
// 上一次是替換狀態,因此不能影響state
state = Object.assign({}, state, partialState);
} else {
// 更新部分狀態,直接將新狀態合併至上一次狀態
state = Object.assign(state, partialState);
}
// 重置標記爲false
dontMutatePrevState = false;
}
}
// 強制當即更新
if (update.isForced) {
queue.hasForceUpdate = true;
}
// 添加回調函數
if (update.callback !== null) {
// Append to list of callbacks.
let callbackList = queue.callbackList;
if (callbackList === null) {
callbackList = queue.callbackList = [];
}
callbackList.push(update);
}
// 遍歷下一個更新任務
update = update.next;
}
// 返回最新的狀態對象
return state;
}
複製代碼
調度器協調,調度的任務主要就是執行組件或組件樹更新,而這些任務則具體由更新器(Updater)完成,能夠說調度器是在整個應用組件樹層面掌控全局,而更新器則深刻到個更具體的每個組件內部執行。
每個組件實例化時都會被注入一個更新器,負責協調組件與React核心進程的通訊,其職責主要能夠歸納爲如下幾點:
更新器實現見ReactFiberClassComponent模塊:
export default function(
scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
computeExpirationForFiber: (fiber: Fiber) => ExpirationTime,
memoizeProps: (workInProgress: Fiber, props: any) => void,
memoizeState: (workInProgress: Fiber, state: any) => void,
) {
// Class component state updater
const updater = {
isMounted,
// 狀態變動,更新入隊列
enqueueSetState(instance, partialState, callback) {
// 獲取fiber
const fiber = ReactInstanceMap.get(instance);
const expirationTime = computeExpirationForFiber(fiber);
// 建立更新任務
const update = {
expirationTime,
partialState,
callback,
isReplace: false,
isForced: false,
nextCallback: null,
next: null,
};
// 添加更新任務至fiber
insertUpdateIntoFiber(fiber, update);
// 調用調度器API以調度fiber任務
scheduleWork(fiber, expirationTime);
},
// 替換狀態時
enqueueReplaceState(instance, state, callback) {
const fiber = ReactInstanceMap.get(instance);
const expirationTime = computeExpirationForFiber(fiber);
const update = {
expirationTime,
partialState: state,
callback,
isReplace: true,
isForced: false,
nextCallback: null,
next: null,
};
// 添加更新任務至fiber
insertUpdateIntoFiber(fiber, update);
scheduleWork(fiber, expirationTime);
},
// 強制更新
enqueueForceUpdate(instance, callback) {
const fiber = ReactInstanceMap.get(instance);
const expirationTime = computeExpirationForFiber(fiber);
const update = {
expirationTime,
partialState: null,
callback,
isReplace: false,
isForced: true,
nextCallback: null,
next: null,
};
insertUpdateIntoFiber(fiber, update);
scheduleWork(fiber, expirationTime);
},
};
// 調用組件實例生命週期方法並調用更新器API
function callComponentWillReceiveProps(
workInProgress, instance, newProps, newContext
) {
const oldState = instance.state;
instance.componentWillReceiveProps(newProps, newContext);
if (instance.state !== oldState) {
// 調用更新器入隊列方法
updater.enqueueReplaceState(instance, instance.state, null);
}
}
// 設置Class組件實例的更新器和fiber
function adoptClassInstance(workInProgress, instance): {
// 設置更新器
instance.updater = updater;
workInProgress.stateNode = instance;
// 設置fiber
ReactInstanceMap.set(instance, workInProgress);
}
// 實例化Class組件實例
function constructClassInstance(workInProgress, props) {
const ctor = workInProgress.type;
const unmaskedContext = getUnmaskedContext(workInProgress);
const needsContext = isContextConsumer(workInProgress);
const context = needsContext
? getMaskedContext(workInProgress, unmaskedContext)
: emptyObject;
// 實例化組件類型
const instance = new ctor(props, context);
// 設置Class實例的更新器和fiber
adoptClassInstance(workInProgress, instance);
return instance;
}
// 掛載組件實例
function mountClassInstance(
workInProgress, renderExpirationTime) {
if (typeof instance.componentWillMount === 'function') {
callComponentWillMount(workInProgress, instance);
}
}
// 更新組件實例
function updateClassInstance(
current, workInProgress, renderExpirationTime
) {
// 組件實例
const instance = workInProgress.stateNode;
// 原Props或新Props
const oldProps = workInProgress.memoizedProps;
let newProps = workInProgress.pendingProps;
if (!newProps) {
// 沒有新Props則直接使用原Props
newProps = oldProps;
}
if (typeof instance.componentWillReceiveProps === 'function' &&
(oldProps !== newProps)) {
// 調用方法進行更新器相關處理
callComponentWillReceiveProps(
workInProgress, instance, newProps
);
}
// 根據原狀態對象和更新隊列計算獲得新狀態對象
const oldState = workInProgress.memoizedState;
let newState;
if (workInProgress.updateQueue !== null) {
// 處理更新隊列更新,計算獲得新State對象
newState = processUpdateQueue(
current,
workInProgress,
workInProgress.updateQueue,
instance,
newProps,
renderExpirationTime,
);
} else {
newState = oldState;
}
// 檢查是否須要更新組件
const shouldUpdate = checkShouldComponentUpdate(...);
if (shouldUpdate) {
if (typeof instance.componentWillUpdate === 'function') {
instance.componentWillUpdate(newProps, newState, newContext);
}
}
// 調用生命週期方法
...
return shouldUpdate;
}
return {
adoptClassInstance,
constructClassInstance,
mountClassInstance,
updateClassInstance
};
}
複製代碼
主要實現如下幾個功能:
初始化組件實例併爲其設置fibre實例和更新器;
初始化或更新組件實例,根據更新隊列計算獲得新狀態等;
調用組件實例生命週期方法,而且調用更新器API更新fiber實例等,如更新組件實例調用的callComponentWillReceiveProps
方法,該方法調用組件實例的componentWillReceiveProps
生命週期方法,並調用更新器updater.enqueueReplaceState
方法,更新fiber實例,並將更新添加至更新隊列:
// 調用組件實例生命週期方法並調用更新器API
function callComponentWillReceiveProps(
workInProgress, instance, newProps, newContext
) {
const oldState = instance.state;
instance.componentWillReceiveProps(newProps, newContext);
if (instance.state !== oldState) {
// 調用更新器入隊列方法
updater.enqueueReplaceState(instance, instance.state, null);
}
}
複製代碼
另外須要重點關注的是insertUpdateIntoFiber
方法,該方法實現將更新任務添加至組件fiber實例,內部會處理將任務添加至fiber更新隊列,源碼見上文更新隊列中介紹的ReactFiberUpdateQueue模塊,最終仍是調用insertUpdateIntoQueue
。
獲取fiber實例比較簡單,fiber實例經過ReactInstanceMap
模塊提供的API進行維護:
export function get(key) {
return key._reactInternalFiber;
}
export function set(key, value) {
key._reactInternalFiber = value;
}
複製代碼
使用節點上的_reactInternalFiber
屬性維護fiber實例,調用get
方法便可獲取。
fiber實例的優先級是由調度器控制,因此須要詢問調度器關於當前fiber實例的優先級,調度器提供computeExpirationForFiber
獲取特定fiber實例的優先級,即獲取特色fiber實例的到期時間(expirationTime),方法具體實現見調度器與優先級章節。
組件狀態變動時,將對應的組件更新任務劃分優先級並根據優先級從高到低依次推入fiber實例的更新隊列,諸如使用setState
方法觸發的更新任務一般是添加至更新隊列尾部。
調度器完成切分任務爲任務單元后,將使用performUnitOfWork
方法開始處理任務單元,而後按調用組件的更新器(實現見上文介紹)相關API,按優先級將任務單元添加至fiber實例的更新隊列:
從work-in-progress的alternate屬性獲取當前穩定fiber,而後調用beginWork
開始處理更新;
// 處理任務單元
function performUnitOfWork(workInProgress: Fiber): Fiber | null {
// 當前最新版本fiber實例使用fiber的alternate屬性獲取
const current = workInProgress.alternate;
// 開始處理,返回子組件fiber實例
let next = beginWork(current, workInProgress, nextRenderExpirationTime);
if (next === null) {
// 不存在子級fiber,完成單元任務的處理,以後繼續處理下一個任務
next = completeUnitOfWork(workInProgress);
}
return next;
}
複製代碼
beginWork
返回傳入fiber實例的子組件fiber實例,,若爲空,則表明此組件樹任務處理完成,不然會在workLoop
方法內迭代調用performUnitOfWork
方法處理:
deadline
:是調用requestIdleCallback
API執行任務處理函數時返回的幀時間對象;nextUnitOfWork
:下一個要處理的任務單元;shouldYield
:判斷是否暫停當前任務處理過程;function workLoop(expirationTime) {
// 渲染更新至DOM的到期時間值 小於 調度開始至開始處理此fiber的時間段值
// 說明任務已通過期
if (nextRenderExpirationTime <= mostRecentCurrentTime) {
// Flush all expired work, 處理全部已經到期的更新
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// Flush asynchronous work until the deadline runs out of time.
// 依次處理異步更新,直至deadline到達
while (nextUnitOfWork !== null && !shouldYield()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
// 處理異步任務時, 調和器將詢問渲染器是否暫停執行;
// 在DOM中,使用requestIdleCallback API實現
function shouldYield() {
if (deadline === null) {
return false;
}
if (deadline.timeRemaining() > 1) {
// 這一幀幀還有剩餘時間,不須要暫停;
// 只有非過時任務能夠到達此判斷條件
return false;
}
deadlineDidExpire = true;
return true;
}
複製代碼
beginWork
方法內根據組件類型調用不一樣方法,這些方法內調用更新器API將更新添加至更新隊列,具體實現見ReactFiberBeginWork模塊:
// 引入更新器模塊
import ReactFiberClassComponent from './ReactFiberClassComponent';
export default function(
config, hostContext, hydrationContext,
scheduleWork: (fiber: Fiber, expirationTime: ExpirationTime) => void,
computeExpirationForFiber: (fiber: Fiber) => ExpirationTime,
) {
// 初始化更新器模塊,獲取API
const {
adoptClassInstance, constructClassInstance,
mountClassInstance, updateClassInstance
} = ReactFiberClassComponent(
scheduleWork, computeExpirationForFiber,
memoizeProps, memoizeState
);
// beginWork,開始任務處理
function beginWork(
current, workInProgress, renderExpirationTime
) {
switch (workInProgress.tag) {
// 對應不一樣類型fiber,執行不一樣處理邏輯
case IndeterminateComponent:
...
case FunctionalComponent:
return updateFunctionalComponent(current, workInProgress);
case ClassComponent:
// 更新類組件,返回子級fiber實例
return updateClassComponent(
current, workInProgress, renderExpirationTime
);
case HostRoot:
return updateHostRoot(current, workInProgress, renderExpirationTime);
case HostComponent:
...
case HostText:
return updateHostText(current, workInProgress);
case CallHandlerPhase:
// This is a restart. Reset the tag to the initial phase.
workInProgress.tag = CallComponent;
case CallComponent:
...
case ReturnComponent:
// A return component is just a placeholder, we can just run through the
// next one immediately.
return null;
case HostPortal:
...
case Fragment:
return updateFragment(current, workInProgress);
default:;
}
}
return {
beginWork,
beginFailedWork
};
}
複製代碼
引入ReactFiberClassComponent
更新器相關模塊並初始化得到API;
beginWork
方法內根據傳入的work-in-progress的fiber類型(tag)調用不一樣邏輯處理;
在邏輯處理裏面會調用更新期API,將更新添加至更新隊列;
以ClassComponent
爲例,將調用updateClassComponent
方法:
判斷若第一次則初始化並掛載組件實例,不然調用updateClassInstance
方法更新組件實例;
最後調用finishClassComponent
方法,調和處理其子組件並返回其子級fiber實例;
// 更新類組件
function updateClassComponent(
current, workInProgress, renderExpirationTime
) {
let shouldUpdate;
if (current === null) {
if (!workInProgress.stateNode) {
// fiber沒有組件實例時須要初始化組件實例
constructClassInstance(workInProgress, workInProgress.pendingProps);
// 掛載組件實例
mountClassInstance(workInProgress, renderExpirationTime);
// 默認須要更新
shouldUpdate = true;
}
} else {
// 處理實例更新並返回是否須要更新組件
shouldUpdate = updateClassInstance(
current,
workInProgress,
renderExpirationTime,
);
}
// 更新完成後,返回子組件fiber實例
return finishClassComponent(
current, workInProgress, shouldUpdate, hasContext
);
}
// 類組件更新完成
function finishClassComponent(
current, workInProgress, shouldUpdate, hasContext
) {
if (!shouldUpdate) {
// 明確設置不須要更新時,不處理更新,
// 如shouldCOmponentUpdate方法return false
return bailoutOnAlreadyFinishedWork(current, workInProgress);
}
const instance = workInProgress.stateNode;
// 從新渲染
ReactCurrentOwner.current = workInProgress;
// 返回組件子組件樹等內容
let nextChildren = instance.render();
// 調和子組件樹,將迭代處理每個組件
// 函數內將調用ReactChildFiber模塊提供的API
reconcileChildren(current, workInProgress, nextChildren);
// 返回子組件fiber實例
return workInProgress.child;
}
複製代碼
上一節更新器已經能按照優先級將更新添加至更新隊列,那麼如何調度執行更新任務呢?
在更新器實現ReactFiberClassComponent模塊中,在enqueueSetState
,enqueueReplaceState
和enqueueForceUpdate
入隊列方法中,均會調用以下方法:
insertUpdateIntoFiber(fiber, update);
scheduleWork(fiber, expirationTime);
複製代碼
insertUpdateIntoFiber
:將更新添加至fiber實例,最終會添加至更新隊列;scheduleWork
:調度任務,傳入fiber實例和任務到期時間;在調和階段,不涉及任何DOM處理,在處理完更新後,須要渲染模塊將更新渲染至DOM,這也是React應用中虛擬DOM(Virtual DOM)的概念,即全部的更新計算都基於虛擬DOM,計算完後纔將優化後的更新渲染至真實DOM。Fiber使用requestIdleCallback
API更高效的執行渲染更新的任務,實現任務的切分。
本小節針對React渲染模塊及調和算法模塊代碼層關係作簡要探討,不感興趣能夠跳過此劫(節)。
在項目中,若是要將應用渲染至頁面,一般會有以下代碼:
import ReactDOM from 'react-dom';
import App form './App'; // 應用根組件
ReactDOM.render(
<App>,
document.querySelector('#App') // 應用掛載容器DOM
);
複製代碼
react-dom
模塊就是適用於瀏覽器端渲染React應用的渲染方案,ReactDOM模塊源碼結構如:
const ReactDOM = {
render(
element: React$Element<any>, // React元素,一般是項目根組件
container: DOMContainer, // React應用掛載的DOM容器
callback: ?Function, // 回調函數
) {
return renderSubtreeIntoContainer(
null,
element,
container,
false,
callback,
);
}
};
複製代碼
經常使用的渲染組件至DOM的render
方法如上,調用renderSubtreeIntoContainer
方法,渲染組件的子組件樹:
// 渲染組件的子組件樹至父容器
function renderSubtreeIntoContainer(
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback: ?Function,
) {
let root = container._reactRootContainer;
if (!root) {
// 初次渲染時初始化
// 建立react根容器
const newRoot = DOMRenderer.createContainer(container, shouldHydrate);
// 緩存react根容器至DOM容器的reactRootContainer屬性
root = container._reactRootContainer = newRoot;
// 初始化容器相關
// Initial mount should not be batched.
DOMRenderer.unbatchedUpdates(() => {
DOMRenderer.updateContainer(children, newRoot, parentComponent, callback);
});
} else {
// 若是不是初次渲染則直接更新容器
DOMRenderer.updateContainer(children, root, parentComponent, callback);
}
// 返回根容器fiber樹的根fiber實例
return DOMRenderer.getPublicRootInstance(root);
}
複製代碼
DOMRenderer
是調用調和算法返回的DOM渲染器對象,在此處會傳入渲染模塊的渲染UI操做API,如:
// 調用調和算法方法
const DOMRenderer = ReactFiberReconciler(
// 傳遞至調和算法中的渲染UI(react-dom模塊即DOM)
// 實際操做API
{
getPublicInstance(instance) {
return instance;
},
createInstance(
type: string,
props: Props,
rootContainerInstance: Container,
hostContext: HostContext,
internalInstanceHandle: Object,
) {
// 建立DOM元素
const domElement = createElement(
type,
props,
rootContainerInstance,
parentNamespace,
);
precacheFiberNode(internalInstanceHandle, domElement);
updateFiberProps(domElement, props);
return domElement;
},
now: ReactDOMFrameScheduling.now,
mutation: {
// 提交渲染
commitMount(
domElement: Instance,
type: string,
newProps: Props,
internalInstanceHandle: Object,
) {
((domElement: any):
| HTMLButtonElement
| HTMLInputElement
| HTMLSelectElement
| HTMLTextAreaElement).focus();
},
// 提交更新
commitUpdate(
domElement: Instance,
updatePayload: Array<mixed>,
type: string,
oldProps: Props,
newProps: Props,
internalInstanceHandle: Object,
) {
// 更新屬性
updateFiberProps(domElement, newProps);
// 對DOM節點進行Diff算法分析
updateProperties(domElement, updatePayload, type, oldProps, newProps);
},
// 清空文本內容
resetTextContent(domElement: Instance): void {
domElement.textContent = '';
},
// 添加爲子級
appendChild(
parentInstance: Instance,
child: Instance | TextInstance,
): void {
parentInstance.appendChild(child);
}
...
}
});
複製代碼
ReactDOMFrameScheduling.now源碼見Github。
在任務完成時將執行createInstance
方法,而後調用createElement
建立DOM元素並添加至文檔。
調和算法入口:
import ReactFiberScheduler from './ReactFiberScheduler';
import {insertUpdateIntoFiber} from './ReactFiberUpdateQueue';
export default function Reconciler(
// all parameters as config object
// 下文用到的config參數即今後處傳入
getPublicInstance,
createInstance,
...
) {
// 生成調度器API
var {
computeAsyncExpiration, computeExpirationForFiber, scheduleWork,
batchedUpdates, unbatchedUpdates, flushSync, deferredUpdates,
} = ReactFiberScheduler(config);
return {
// 建立容器
createContainer(containerInfo, hydrate: boolean) {
// 建立根fiber實例
return createFiberRoot(containerInfo, hydrate);
},
// 更新容器內容
updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): void {
const current = container.current;
...
// 更新
scheduleTopLevelUpdate(current, element, callback);
},
...
// 獲取容器fiber樹的根fiber實例
getPublicRootInstance (container) {
// 獲取fiber實例
const containerFiber = container.current;
if (!containerFiber.child) {
return null;
}
switch (containerFiber.child.tag) {
case HostComponent:
return getPublicInstance(containerFiber.child.stateNode);
default:
return containerFiber.child.stateNode;
}
},
unbatchedUpdates
}
}
複製代碼
在react-dom
渲染模塊調用createContainer
建立容器和根fiber實例,FiberRoot對象,調用updateContainer
方法更新容器內容。
// 更新
function scheduleTopLevelUpdate(
current: Fiber,
element: ReactNodeList,
callback: ?Function,
) {
callback = callback === undefined ? null : callback;
const update = {
expirationTime,
partialState: {element},
callback,
isReplace: false,
isForced: false,
nextCallback: null,
next: null,
};
// 更新fiber實例
insertUpdateIntoFiber(current, update);
// 執行任務
scheduleWork(current, expirationTime);
}
複製代碼
調用scheduleWork
方法處理更新任務,實現見上文,源碼。
處理完更新後須要確認提交更新至渲染模塊,而後渲染模塊才能將更新渲染至DOM。
import ReactFiberCommitWork from './ReactFiberCommitWork';
const {
commitResetTextContent,
commitPlacement,
commitDeletion,
commitWork,
commitLifeCycles,
commitAttachRef,
commitDetachRef,
} = ReactFiberCommitWork(config, captureError);
function commitRoot(finishedWork) {
...
commitAllHostEffects();
}
// 循環執行提交更新
function commitAllHostEffects() {
while (nextEffect !== null) {
let primaryEffectTag =
effectTag & ~(Callback | Err | ContentReset | Ref | PerformedWork);
switch (primaryEffectTag) {
case Placement: {
commitPlacement(nextEffect);
nextEffect.effectTag &= ~Placement;
break;
}
case PlacementAndUpdate: {
// Placement
commitPlacement(nextEffect);
nextEffect.effectTag &= ~Placement;
// Update
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Update: {
const current = nextEffect.alternate;
commitWork(current, nextEffect);
break;
}
case Deletion: {
isUnmounting = true;
commitDeletion(nextEffect);
isUnmounting = false;
break;
}
}
nextEffect = nextEffect.nextEffect;
}
}
// Flush sync work.
let finishedWork = root.finishedWork;
if (finishedWork !== null) {
// This root is already complete. We can commit it.
root.finishedWork = null;
root.remainingExpirationTime = commitRoot(finishedWork);
}
複製代碼
提交更新是最後確認更新組件的階段,主要邏輯以下:
export default function (mutation, ...) {
const {
commitMount,
commitUpdate,
resetTextContent,
commitTextUpdate,
appendChild,
appendChildToContainer,
insertBefore,
insertInContainerBefore,
removeChild,
removeChildFromContainer,
} = mutation;
function commitWork(current: Fiber | null, finishedWork: Fiber): void {
switch (finishedWork.tag) {
case ClassComponent: {
return;
}
case HostComponent: {
const instance: I = finishedWork.stateNode;
if (instance != null) {
// Commit the work prepared earlier.
const newProps = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldProps = current !== null ? current.memoizedProps : newProps;
const type = finishedWork.type;
// TODO: Type the updateQueue to be specific to host components.
const updatePayload = finishedWork.updateQueue:;
finishedWork.updateQueue = null;
if (updatePayload !== null) {
commitUpdate(
instance,
updatePayload,
type,
oldProps,
newProps,
finishedWork,
);
}
}
return;
}
case HostText: {
const textInstance = finishedWork.stateNode;
const newText = finishedWork.memoizedProps;
// For hydration we reuse the update path but we treat the oldProps
// as the newProps. The updatePayload will contain the real change in
// this case.
const oldText: string =
current !== null ? current.memoizedProps : newText;
commitTextUpdate(textInstance, oldText, newText);
return;
}
case HostRoot: {
return;
}
default: {
}
}
}
}
複製代碼