const ReactDom: Object = {
// 服務端渲染使用hydrate 和render惟一的區別是 第四個參數的區別
hydrate(
element: React$Element<any>,
container: DOMContainer,
callback: ?Function,
){
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
true,
callback,
);
},
render(
element: React$Element<any>, // reactELement
container: DOMContainer, // 要掛載到哪一個dom節點
callback: ?Function, //應用渲染結束後 調用callback
){
return legacyRenderSubtreeIntoContainer(
null, // parentComponent
element,
container,
false,
callback,
)
}
}
複製代碼
// 渲染Dom Tree到掛載的container節點上
function legacyRenderSubtreeIntoContainer( parentComponent: ?React$Component<any,any>, children: ReactNodeList, container: DomContainer, forceHydrate: boolean, callback: ?function ){
// container 是咱們傳入的dom節點 判斷container 是否有_reactRootContainer屬性
// 正常狀況下剛開始寫入組裝的dom標籤是不會有_reactRootContainer屬性的 因此第一次渲染 根節點root是不存在的
let root: _ReactSyncRoot = (container._reactRootContainer: any);
let fiberRoot;
if(!root){
// 建立一個root對象並賦值container._reactRootContainer
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate
)
// 結合 ReactSyncRoot 函數 this._internalRoot = root; fiberRoot 爲 createContainer函數返回結果
fiberRoot = root._internalRoot;
// Initial mount should not be batched. 不使用批量更新
unbatchedUpdates(() => {
updateContainer(children, fiberRoot, parentComponent, callback);
});
}
return getPublicRootInstance(fiberRoot);
}
function getPublicRootInstance( container: OpaqueRoot, ): React$Component<any, any> | PublicInstance | null {
// containerFiber 這裏是一個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;
}
}
複製代碼
function legacyCreateRootFromDOMContainer( container: DOMContainer, forceHydrate: boolean, ): _ReactSyncRoot {
// 是否須要複用老節點並和新渲染的節點合併
const shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
if (!shouldHydrate) {
let rootSibling;
// 刪除container 下的全部子節點
while((rootSibling = container.lastChild)){
container.removeChild(rootSibling);
}
}
return new ReactSyncRoot(
container,
LegacyRoot,
shouldHydrate ? {
hydrate: true,
}
: undefined,
)
}
複製代碼
// 若是 forceHydrate爲false 則在render的時候調用shouldHydrateDueToLegacyHeuristic方法
// ROOT_ATTRIBUTE_NAME : 'data-reactroot' 爲老版本的服務端渲染 的常量
// 會在root節點下的第一個節點加上 data-reactroot 屬性標識目前應用是否有服務端渲染
function shouldHydrateDueToLegacyHeuristic (container){
const rootElment = getReactRootElementInContainer(getReactRootElementInContainer);
return !!(
rootElement &&
rootElement.nodeType === ELEMENT_NODE &&
rootElement.hasAttribute(ROOT_ATTRIBUTE_NAME)
);
}
// 若是container.nodeType是 DOCUMENT_NODE 則返回 document
// 不然返回第一個子節點 經過判斷 是否返回document 來判斷是否須要一個 Hydrate
function getReactRootElementInContainer (container:any) {
if(container.nodeType === DOCUMENT_NODE){
return container.documentElement;
}else{
return container.firstChild;
}
}
複製代碼
function ReactSyncRoot ( container: DOMContainer, tag: RootTag, options: void | RootOptions, ) {
// Tag is either LegacyRoot or Concurrent Root
const hydrate = options != null && options.hydrate === true;
const hydrationCallbacks =
(options != null && options.hydrationOptions) || null;
// 建立了一個 fiberRoot 進行賦值
const root = createContainer(container, tag, hydrate, hydrationCallbacks);
this._internalRoot = root;
}
複製代碼
function updateContainer ( element:ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any,any>, callback: ?Function, ) {
// container.current 對應一個fiber對象
const current = container.current;
const currentTime = requestCurrentTime();
const suspenseConfig = requestCurrentSuspenseConfig();
// expirationTime 很是重要 優先級任務更新 擁有複雜計算
const expiriationTime = computeExpiriiationForFiber(currentTime,current,suspenseConfig);
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
suspenseConfig,
callback,
);
}
複製代碼
//
function updateContainerAtExpirationTime ( element: ReactNodeList, container: OpaqueRoot, parentComponent: ?React$Component<any, any>, expirationTime: ExpirationTime, suspenseConfig: null | SuspenseConfig, callback: ?Function, ) {
//
return scheduleRootUpdate(
current,
element,
expirationTime,
suspenseConfig,
callback,
);
}
複製代碼
function scheduleRootUpdate( current: Fiber, element: ReactNodeList, expirationTime: ExpirationTime, suspenseConfig: null | SuspenseConfig, callback: ?Function, ) {
// 建立 update對象 用來標記react應用 須要更新的地點
const update = createUpdate(expiriationTime,suspenseConfig);
// 設置update屬性 初次渲染
update.payload = {element};
// enqueueUpdate 把 update對象加入到fiber對象上對應的 update enqueue 裏
// setState forceUpdate的時候都會調用
enqueueUpdate(current, update);
// 開始進行任務調度 任務優先級概念 在同一時間有不一樣的任務優先級的任務在隊列裏,
// 則須要有一個任務調度器在裏面按照優先級先執行優先級高的任務再執行任務優先級低的任務
scheduleWork(current, expirationTime);
return expirationTime;
}
複製代碼
執行節點調和、任務調度的操做javascript
react-reconciler/src/ReactFiberRoot.js
react-reconciler/src/ReactFiber.js
react-reconciler/src/ReactUpdateQueue.jsjava
function createFiberRoot( containerInfo:any, tag: RootTag, hydrate: boolean, hydrationCallbacks: null | SuspenseHydrationCallbacks, ): FiberRoot {
const root: FiberRoot = (new FiberRootNode(containerInfo,tag,hydrate) : any);
if (enableSuspenseCallback) {
root.hydrationCallbacks = hydrationCallbacks;
}
// root節點的fiber
// current 是 uninitializedFiber
// 每個react element節點都會對應一個fiber對象 所以每個fiber對象也會有一個樹結構
// root.current 是這個fiber對象樹結構的頂點
const uninitializedFiber = createHostRootFiber(tag);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root;
return root;
}
// FiberRoot 對象上有哪些信息 屬性
function FiberRootNode(containerInfo, tag, hydrate) {
this.tag = tag;
// 對應root節點 對應的fiber對象
this.current = null;
// react dom上的root節點 render裏接收的第二個參數
this.containerInfo = containerInfo;
// 只有在持久的更新中會用到 react-dom中不會被用到
this.pendingChildren = null;
this.pingCache = null;
this.finishedExpirationTime = NoWork;
// 用來記錄一個更新渲染當中 完成了的渲染更新任務 由於在整個樹當中會存在各類不一樣的更新任務
// 每個更新渲染咱們都會先渲染優先級最高的任務 優先級最高的任務渲染完成以後 就會是一個finishedWork
// 標記在應用的root上 更新完以後咱們要吧應用輸出到dom節點上面 輸出的過程中就是讀取finishedWork屬性
this.finishedWork = null;
// suspense 在renderFunction裏 throw一個 promise 任務會被掛起 以後渲染suspense 組件的callback
// 等到promise result以後就會把result以後的數據顯示出來 timeoutHandle來幫助記錄這個過程中超時的狀況
this.timeoutHandle = noTimeout;
// 只有 ·renderSubtreeIntoContainer·時候 纔會有用 頂層context對象
this.context = null;
this.pendingContext = null;
// 應用是否要和原來的dom節點進行合併的標誌
this.hydrate = hydrate;
this.firstBatch = null;
this.callbackNode = null;
this.callbackExpirationTime = NoWork;
this.firstPendingTime = NoWork;
this.lastPendingTime = NoWork;
this.pingTime = NoWork;
if (enableSchedulerTracing) {
this.interactionThreadID = unstable_getThreadID();
this.memoizedInteractions = new Set();
this.pendingInteractionMap = new Map();
}
if (enableSuspenseCallback) {
this.hydrationCallbacks = null;
}
}
複製代碼
react-fiberRoot 會建立 一個fibernode
double buffer 參考連接 react-fiber 經過單向鏈表的數據結構把整個fiber樹串聯起來,而後提供一個高效、方便的遍歷方式 react
圖一中的 uninitializedFiber 是指圖二的 RootFiber RootFiber 能夠經過child 拿到整個app tree節點屬性 在fiber tree的遍歷過程中,只會記錄第一個子節點A,其餘的子節點都會被當作A的兄弟節點sibling存在。當找不到sibling和child的時候將再也不遍歷typescript
// fiber對應一個被處理或者已經處理了的組件 一個組件能夠有一個或者多個Fiber
export type Fiber = {|
// tag用於區分 fiber的不一樣類型,標記不一樣的組件類型
tag: WorkTag,
// ReactElement裏面的key
key: null | string,
// ReactElement.type,也就是咱們調用`createElement`的第一個參數
elementType: any,
// 異步組件resolved以後返回的內容,通常是`function`或者`class`
type: any,
// 對應節點的實例 class Component 對應的是class Component實例,Dom節點就對應的Dom節點的實例。function Component沒有實例 因此沒有stateNode
stateNode: any,
return: Fiber | null,
// 單鏈表結構樹結構.
child: Fiber | null,
sibling: Fiber | null,
index: number,
// The ref last used to attach this node.
// I'll avoid adding an owner field for prod and model that as functions.
ref: null | (((handle: mixed) => void) & {_stringRef: ?string}) | RefObject,
// 在setState以後 pendingProps 裏存的新的props memoizedProps存的老的props
pendingProps: any,
memoizedProps: any,
// 在setState或者forceUpdate 建立更新以後就會存在此隊列裏
updateQueue: UpdateQueue<any> | null,
memoizedState: any,
// Dependencies (contexts, events) for this fiber, if it has any
dependencies: Dependencies | null,
mode: TypeOfMode,
// Effect
// 標記最終dom節點要進行哪些更新的工具
// 標記是否執行組件生命週期的內容
effectTag: SideEffectTag,
nextEffect: Fiber | null,
// The first and last fiber with side-effect within this subtree. This allows
// us to reuse a slice of the linked list when we reuse the work done within
// this fiber.
firstEffect: Fiber | null,
lastEffect: Fiber | null,
// 當前任務調度產生的過時時間
expirationTime: ExpirationTime,
childExpirationTime: ExpirationTime,
// current <=> wokInprogress 在開始更新和 更新到dom節點上時候 進行狀態交換 不須要每次更新都建立 新的對象
// 一個複製的過程來保持這個兩個對象都存在 在react當中叫 double buffer
// fiber與workInProgress互相持有引用,把current指針指向workInProgress tree,丟掉舊的fiber tree。
// 舊fiber就做爲新fiber更新的預留空間,達到複用fiber實例的目的。
alternate: Fiber | null,
actualDuration?: number,
actualStartTime?: number,
selfBaseDuration?: number,
treeBaseDuration?: number,
_debugID?: number,
_debugSource?: Source | null,
_debugOwner?: Fiber | null,
_debugIsCurrentlyTiming?: boolean,
_debugNeedsRemount?: boolean,
_debugHookTypes?: Array<HookType> | null,
|};
複製代碼
function createUpdate ( expirationTime:ExpirationTime, suspenseConfig:null | SuspenseConfig, ): Update<*>{
let update:Update<*> = {
expirationTime,
suspenseConfig,
tag:UpdateState,
payload:null,
callback:null,
next:null,
nextEffect:null,
}
}
複製代碼
export type Update<State> = {
/** 更新的過時時間 */
expirationTime: ExpirationTime,
/** Suspense組件參數配置 */
suspenseConfig: null | SuspenseConfig,
/** * 指定更新類型,值爲如下幾種 * export const UpdateState = 0; 更新state * export const ReplaceState = 1;替換state * export const ForceUpdate = 2;強制更新state * export const CaptureUpdate =3; * 在渲染的過程中若是出現渲染錯誤被捕獲了, ErrorBoundary * 組件捕獲渲染錯誤後經過 CaptureUpdate 狀態 從新渲染ErrorBoundary內節點 */
tag: 0 | 1 | 2 | 3,
/** * 要更新的內容 * 例如 把整個element Dom Tree 節點 渲染到payload節點上 */
payload: any,
/** 對應回調 例如 setState接收的第一個參數 render都有 */
callback: (() => mixed) | null, /** * 指向下一個更新 * update是總體存放在updateQueue中,updateQueue是一個相似於單項鍊表的結構 * 每個update都有一個next,這個next指向下一個update,updateQueue會有一個 * firstUodate和lastUpdate,記錄這個單項鍊表的開頭和結尾,這中間的開頭和結尾都 * 是經過next串聯起來一一對應,把整個update 單鏈表的結構鏈接起來 * 在updateQueue中 先讀取firstUodate對應的update,而後從第一個update的next查找下一個update,直到讀取到lastUpdate爲止 這就是updateQueue隊列的執行順序 */ next: Update<State> | null, /** 指向下一個更新 nextEffect: Update */ nextEffect: Update<State> | null, //DEV only priority?: ReactPriorityLevel, }; export type UpdateQueue<State> = { // 每次應用渲染更新完成以後 baseState記錄最新state 在下次更新時候直接讀取baseState // 下次計算更新直接在此基礎上計算 而不是獲取最初state baseState: State, // 隊列裏的第一個update firstUpdate: Update<State> | null, // 隊列裏的最後一個update lastUpdate: Update<State> | null, // 第一個捕獲類型的 update firstCapturedUpdate: Update<State> | null, // 最後一個捕獲類型的 update lastCapturedUpdate: Update<State> | null, firstEffect: Update<State> | null, lastEffect: Update<State> | null, firstCapturedEffect: Update<State> | null, lastCapturedEffect: Update<State> | null, }; 複製代碼
// 建立或者更新fiber 上的update enqueue
// 先 判斷 fiber 上alternate 是否有緩存的舊fiber 若是有 則建立兩個隊列
// 若是兩個隊列相同 或只有一個隊列 則把update對象推入queue1中
// 若是兩個隊列不一樣 則將update更新入兩個queue中
function enqueueUpdate<State>( fiber:Fiber, update:Update<State> ){
// Update queues are created lazily.
const alternate = fiber.alternate;
let queue1;
let queue2;
if(alternate === null){
// 在這裏理解爲 alternate至關於 workInProgress ,fiber 至關於 current
// 若是alternate 不存在 則表明 這是第一次建立
// 第一次渲染 reactDom.render的時候執行
queue1 = fiber.updateQueue;
queue2 = null;
// 若是此時在初始化 沒有updateQueue 則建立updateQueue
if(queue1 === null){
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState)
}
}else{
// There are two owners.
// workInProgress 存在記錄 則表明建立過了
queue1 = fiber.updateQueue;
queue2 = alternate.updateQueue;
if(queue1 === null){
if(queue2 === null){
queue1 = fiber.updateQueue = createUpdateQueue(fiber.memoizedState);
queue2 = alternate.updateQueue = createUpdateQueue(
alternate.memoizedState,
);
}else{
queue1 = fiber.updateQueue = cloneUpdateQueue(queue2);
}
}else{
if(queue2 === null){
queue2 = alternate.updateQueue = cloneUpdateQueue(queue1);
}
}
}
if(queue2 === null || queue1 === queue2 ){
// 初次渲染 只有一個queue
appendUpdateToQueue(queue1,update)
}else{
// 性能優化 避免重複的update
if (queue1.lastUpdate === null || queue2.lastUpdate === null) {
appendUpdateToQueue(queue1, update);
appendUpdateToQueue(queue2, update);
}else{
// 兩個隊列的lastUpdate 都存在時候 他的lastUpdate 應該是相同的對象,因此 只用將update 推入 queue1
appendUpdateToQueue(queue1, update);
// 改變queue2 lastUpdate 的指針
queue2.lastUpdate = update;
}
}
}
複製代碼
export function createUpdateQueue<State>(baseState: State): UpdateQueue<State> {
const queue: UpdateQueue<State> = {
baseState,
firstUpdate: null,
lastUpdate: null,
firstCapturedUpdate: null,
lastCapturedUpdate: null,
firstEffect: null,
lastEffect: null,
firstCapturedEffect: null,
lastCapturedEffect: null,
};
return queue;
}
複製代碼
// 將當前update對象放入 UpdaQueue的最後
function appendUpdateToQueue<State>( queue: UpdateQueue<State>, update: Update<State>, ) {
// Append the update to the end of the list.
if (queue.lastUpdate === null) {
// Queue is empty 隊列爲空 直接把lastUpdate 賦值爲firstUpdate
queue.firstUpdate = queue.lastUpdate = update;
} else {
// 在queue中添加新的update
// 因此把當前update的lastUpdate的next 指向 新加的update
// 當前隊列queue的lastUpdate 爲新加的update
queue.lastUpdate.next = update;
queue.lastUpdate = update;
}
}
複製代碼
點擊進入 下圖在線demopromise
異步執行的任務優先級都是較低的,爲防止這個任務一直被打斷,一直不能執行,因此react設置了一個expiraton-time。在某一個expiraton-time時間以前該任務能夠被打斷,當時間過時這個任務就會被強制執行。緩存
給節點的fiber 建立更新。reactDom.render是對於root節點的建立更新,setState和forceUpdate針對的是classComponent的狀態進行更新渲染。性能優化