剖析React內部運行機制-結合源碼分析「協調算法」執行過程

原文:Inside Fiber: in-depth overview of the new reconciliation algorithm in Reactreact

譯文:React Fiber 那些事: 深刻解析新的協調算法算法


上面提供了兩篇很是優秀的介紹React協調算法的文章,建議讀者先看原文或者譯文,看完一遍以後若是沒有太深的印象,那麼再從本文提供的角度去思考問題。segmentfault

新的協調算法

協調算法出現的背景

《剖析React內部運行機制-「譯」React組件、元素和實例》 這篇文章中咱們知道:瀏覽器

  • React組件 即開發者所寫的組件,在執行時會被React解析成 React元素
  • React元素 是描述組件實例或DOM節點及其所需屬性的 普通對象
  • 不少不少 React元素 的層層嵌套最終造成了整個應用程序的 「樹」 形結構。

那麼如何將應用程序中衆多的 「React元素」 組裝成 就是協調算法核心工做的一部分。數據結構

注意: 瀏覽器只是React能夠渲染的「渲染環境」之一,其餘主要的目標是經過React native實現的iOS和Android視圖(這就是爲何「虛擬DOM」有點用詞不當)。在 《剖析React內部運行機制-「譯」React Fiber 架構》 這篇文章的 「協調與渲染」 部分有更加詳細的介紹。架構

協調算法所使用的數據結構

React Fiber,其構造函數爲:ide

function FiberNode(tag, pendingProps, key, mode) {
    // 此處屬性用於生成DOM節點實例
    this.tag = tag; // 用於標識組件的類型,好比class組件、function組件、宿主組件等
    this.key = key;
    this.elementType = null;
    this.type = null;
    this.stateNode = null;
    
    // 此處屬性用於協調算法遍歷Fiber節點樹
    this.return = null;
    this.child = null;
    this.sibling = null;
    this.index = 0;
    
    this.ref = null;
    
    this.pendingProps = pendingProps;
    this.memoizedProps = null;
    this.updateQueue = null;
    this.memoizedState = null;
    this.dependencies = null;
    
    this.mode = mode;
    
    // 此處屬性用於反作用(的調用,好比生命週期函數等)
    this.effectTag = NoEffect;
    this.nextEffect = null;
    
    this.firstEffect = null;
    this.lastEffect = null;
    
    this.expirationTime = NoWork;
    this.childExpirationTime = NoWork;
    
    this.alternate = null;
    
    if (enableProfilerTimer) {
        // 下面的操做是爲了不v8性能問題
        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;
    }
    
    {
        this._debugID = debugCounter++;
        this._debugSource = null;
        this._debugOwner = null;
        this._debugIsCurrentlyTiming = false;
        this._debugNeedsRemount = false;
        this._debugHookTypes = null;
        if (!hasBadMapPolyfill && typeof Object.preventExtensions === 'function') {
            Object.preventExtensions(this);
        }
    }
}
複製代碼

固然,React Fiber的屬性用的最多的仍是前半部分。其更加詳細做用在《剖析React內部運行機制-「譯」React Fiber 架構》 這篇文章中有介紹。函數

經過程序看協調算法執行機制

一、協調算法入口函數-renderRootoop

function renderRoot(root, expirationTime, isSync) {
    ...
    // 該方法內部會建立workInProgress,並暴露到外層(即renderRoot)做用域
    prepareFreshStack(root, expirationTime);
    
    ...
    // 開始執行工做循環
    if (isSync) {
      workLoopSync();
    } else {
      workLoop();
    }
    
    ...
    // 檢查工做循環結束後的workInProgress狀態
    switch (workInProgressRootExitStatus) {
        ...
        case RootCompleted:
            return commitRoot.bind(null, root);
    }
}
複製代碼

二、工做循環/組件解析-workLoopSyncpost

function workLoopSync() {
    while (workInProgress !== null) {
        workInProgress = performUnitOfWork(workInProgress);
    }
}
複製代碼

三、執行工做單元-performUnitOfWork

function performUnitOfWork(unitOfWork) {
    var current$$1 = unitOfWork.alternate;
    
    ...
    
    var next = void 0;
    next = beginWork$$1(current$$1, unitOfWork, renderExpirationTime);
    
    ...
    
    return next;
}
複製代碼

四、開始工做--beginWork$$1(其實是執行的是下面beginWork$1)

function beginWork$1(current$$1, workInProgress, renderExpirationTime) {
    
    ...
    
    // 這裏根據當前組件的類型分別返回不一樣的內容
    switch (workInProgress.tag) {
        ...
        
        case FunctionComponent:
            return updateFunctionComponent(current$$1, workInProgress, _Component, resolvedProps, renderExpirationTime);
        case ClassComponent:
            return updateClassComponent(current$$1, workInProgress, _Component2, _resolvedProps, renderExpirationTime);
        case HostRoot:
            return updateHostRoot(current$$1, workInProgress, renderExpirationTime);
        case HostComponent:
            return updateHostComponent(current$$1, workInProgress, renderExpirationTime);
        case HostText:
            return updateHostText(current$$1, workInProgress);
        
        ...
    }
}
複製代碼

函數 beginWork 始終返回指向要在循環中處理的下一個子節點的指針或 null。

五、當匹配到ClassComponent

return updateClassComponent(current$$1, workInProgress, _Component2, _resolvedProps, renderExpirationTime);
複製代碼

更新組件函數-updateClassComponent

function updateClassComponent(current$$1, workInProgress, Component, nextProps, renderExpirationTime) {
    
    ...
    
    var nextUnitOfWork = finishClassComponent(current$$1, workInProgress, Component, shouldUpdate, hasContext, renderExpirationTime);
    
    return nextUnitOfWork;
}
複製代碼

六、結束組件函數-finishClassComponent

function finishClassComponent(current$$1, workInProgress, Component, shouldUpdate, hasContext, renderExpirationTime) {
    
    ...
    
    var instance = workInProgress.stateNode;
    ...
    var nextChildren = void 0;
    ...
    // 這裏會調用React組件裏面的render函數獲取返回值
    nextChildren = instance.render();
    ...
    // 協調算法的核心--查找child節點
    reconcileChildren(current$$1, workInProgress, nextChildren, renderExpirationTime)
    ...
    return workInProgress.child;
}
複製代碼

七、協調算法核心之一-肯定child節點

function reconcileChildren(current$$1, workInProgress, nextChildren, renderExpirationTime) {
  if (current$$1 === null) {
    workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderExpirationTime);
  } else {
    workInProgress.child = reconcileChildFibers(workInProgress, current$$1.child, nextChildren, renderExpirationTime);
  }
}
複製代碼

繼續看下面函數的定義,瞭解workInProgress.child是被賦了什麼值。

// mountChildFibers和reconcileChildFibers調用了同一個方法,分別是首次加載和後續更新時的執行邏輯
var reconcileChildFibers = ChildReconciler(true);
var mountChildFibers = ChildReconciler(false);

function ChildReconciler(shouldTrackSideEffects) {
    function deleteChild(returnFiber, childToDelete) {
        ...
    }
    
    ...
    // 協調單個元素(協調核心方法也會調用這裏哦)
    function reconcileSingleElement(returnFiber, currentFirstChild, element, expirationTime) {
        var key = element.key;
        var child = currentFirstChild;
        
        while (child !== null) {
            ...
            
            child = child.sibling;
        }
        
        if (element.type === REACT_FRAGMENT_TYPE) {
            // 這裏經過函數名就能夠知道,createFiberFromFragment將根據render函數返回值裏面的Fragment元素建立Fiber對象。
            var created = createFiberFromFragment(element.props.children, returnFiber.mode, expirationTime, element.key);
            created.return = returnFiber;
            // 天哪!終於有返回值了,是一個Fiber節點對象
            return created;
        } else {
            // createFiberFromElement將根據render函數返回值裏面的DOM元素建立Fiber對象
            var _created4 = createFiberFromElement(element, returnFiber.mode, expirationTime);
            _created4.ref = coerceRef(returnFiber, currentFirstChild, element);
            _created4.return = returnFiber;
            // 返回一個Fiber節點對象
            return _created4;
        }
    }
    
    ...
    // 這裏纔是核心方法
    function reconcileChildFibers(returnFiber, currentFirstChild, newChild, expirationTime) {
    ...
    // 處理對象類型
    var isObject = typeof newChild === 'object' && newChild !== null;
    if (isObject) {
        switch (newChild.$$typeof) {
            case REACT_ELEMENT_TYPE:
                // 注意這裏哦,會調用上面定義的函數
                return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, expirationTime));
            case REACT_PORTAL_TYPE:
                return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, expirationTime));
        }
    }
    ...
    return reconcileChildFibers;
}
複製代碼

完成每件有意義的事情都會須要咱們不懈的堅持。 若是你堅持看到了這裏,請爲本身點個贊。

在React協調算法執行過程當中,會遇到不少不少的case,在這篇文章中咱們僅僅是對ClassComponent類型的組件抓住一條主線進行深刻的剖析。其餘類型好比HostRootHostComponent等能夠按照上述分析思路進行探索。

總結

文章開頭提供的兩篇文章對「協調算法」作了很好的理論分析,在理論分析的基礎上,在React內部執行到renderRoot()函數內部時,經過逐級剖析源碼中相關函數的主體(省略了不少判斷以及校驗等邏輯),分析了root對象上面的組件如何一步步被解析成對應的React元素樹。

相關文章
相關標籤/搜索