react 源碼解讀3 react-dom

建立更新

建立更新的方式

  1. reactDom.render || reactDom.hydrate
  2. setState
  3. forceUpdate

源碼文件目錄

  1. react/react-dom/src/ReactDom.js reactDom源碼
  2. react/react-dom/src/client 客戶端渲染
  3. react/react-dom/src/server node.js平臺用到服務器端渲染
  4. react/react-dom/src/shared server和client 都會用到的公共包

ReactDom

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,
        )
    }
}
複製代碼

legacyRenderSubtreeIntoContainer

// 渲染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;
  }
}
複製代碼

legacyCreateRootFromDOMContainer

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,
    )
}
複製代碼

shouldHydrateDueToLegacyHeuristic

// 若是 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;
    }
}
複製代碼

ReactSyncRoot

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;
}
複製代碼

updateContainer

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,
  );
}
複製代碼

updateContainerAtExpirationTime

// 
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,
    );
}
複製代碼

scheduleRootUpdate

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;
}
複製代碼

react-reconciler

執行節點調和、任務調度的操做javascript

小結

  1. 建立react-root,同時建立fiber-root並自動初始化一個fiber對象
  2. 在root上建立expiriation-time
  3. expiriation-time建立完以後,建立update更新的對象,把這個更新的對象放在root節點上面以後就進入了一個更新的過程
  4. 調度整個任務更新

react-fiber-root

fiberRoot 概念

  1. 整個應用的起點
  2. 包含應用掛載的目標節點 (container)
  3. 記錄整個應用更新過程中的應用信息。eg:各類不一樣類型的expiriationTime,異步調度過程中的callback

目錄

react-reconciler/src/ReactFiberRoot.js
react-reconciler/src/ReactFiber.js
react-reconciler/src/ReactUpdateQueue.jsjava

createFiberRoot

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-fiber

fiber 是什麼

react-fiberRoot 會建立 一個fibernode

  1. 每個ReactElment對應一個fiber對象
  2. 記錄節點的各類狀態,例如Class Component裏的props、state都是記錄在fiber對象上的,在fiber更新以後纔會更新到Class Component的this.state,this.props裏面。實際上更新state和props不是在class Component上更新的。而是在fiber對象這個節點更新以後才把這個屬性放到this上。由於這纔給react實現hooks提供方便。由於hooks用在 function Component上,function Component是沒有this的,是在fiber對象更新後,再讓function Component拿到這個更新了的state和props
  3. 串聯整個應用造成樹結構。在fiber裏能夠記錄整個應用的狀態,把每一個節點串聯起來

fiber tree 單向鏈表的串聯方式

double buffer 參考連接 react-fiber 經過單向鏈表的數據結構把整個fiber樹串聯起來,而後提供一個高效、方便的遍歷方式 react

圖一中的 uninitializedFiber 是指圖二的 RootFiber RootFiber 能夠經過child 拿到整個app tree節點屬性 在fiber tree的遍歷過程中,只會記錄第一個子節點A,其餘的子節點都會被當作A的兄弟節點sibling存在。當找不到sibling和child的時候將再也不遍歷typescript

fiber 的數據結構 含義

// 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,
|};
複製代碼

react-update-and-updateQueue

什麼是update

  1. 用於記錄組件狀態的改變的對象
  2. 存放於 fiber對象 UpdateQueue中 ,UpdateQueue是一個單向鏈表的結構。一個Queue中可能會存在多個update,在此次更新當中會根據這些update結果算出最終的新的state的結果
  3. 多個Update能夠同時存在

Update的建立

function createUpdate ( expirationTime:ExpirationTime, suspenseConfig:null | SuspenseConfig, ): Update<*>{
    let update:Update<*> =  {
   
        expirationTime,
   
        suspenseConfig,
   
        tag:UpdateState,
   
        payload:null,
    
        callback:null,
    
        next:null,
    
        nextEffect:null,
    }
}
複製代碼

Update && updateQueue 的數據結構

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指向下一個updateupdateQueue會有一個 * firstUodatelastUpdate,記錄這個單項鍊表的開頭和結尾,這中間的開頭和結尾都 * 是經過next串聯起來一一對應,把整個update 單鏈表的結構鏈接起來 * 在updateQueue中 先讀取firstUodate對應的update,而後從第一個updatenext查找下一個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, }; 複製代碼

enqueueUpdate

// 建立或者更新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;
        }
    }
    
}
複製代碼

createUpdateQueue

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;
}
複製代碼

appendUpdateToQueue

// 將當前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-expiriation-time

expiriation-time是什麼

異步執行的任務優先級都是較低的,爲防止這個任務一直被打斷,一直不能執行,因此react設置了一個expiraton-time。在某一個expiraton-time時間以前該任務能夠被打斷,當時間過時這個任務就會被強制執行。緩存

expiriationTime的計算方式

diffrent-expiraton-time

expiriation-time種類

  1. Sync模式:優先級最高的。該任務建立更新完成以後就要立馬更新到dom節點上。是一個建立即更新的流程
  2. 異步模式:會進行調度,會有一系列複雜的操做在裏面,可能會被中斷,他會有一個計算的過時時間,根據優先級級別來計算對應的過時時間。
  3. 指定context

常量定義 -額外發現

二進制字面量 + 異或 進行組合判斷 二進制字面量

react-setState-forceUpdate

setState和forceUpdate的核心

給節點的fiber 建立更新。reactDom.render是對於root節點的建立更新,setState和forceUpdate針對的是classComponent的狀態進行更新渲染。性能優化

相關文章
相關標籤/搜索