React 6.2のfiberアーキテクチャ

20615 ワード

React 16は本当に一日ごとに変わります.今見ないと、後で分かりにくくなります.
React 16では、JSXコンパイルによって一つの仮想DOMオブジェクトが得られたが、これらの仮想DOMオブジェクトの再加工は、天地を覆すような変化を経ている.私たちは底を追求して、どうやって一歩ずつ変えてきたのかを見ます.私たちはまずコンポーネントを見ません.まずReactDOM.renderを見つけます.ReactDOMのソースコードには、似たようなものが三つあります.
//by     ,   :370262116     React anujs
// https://github.com/RubyLouvre/anu    star

ReactDOM= {
 hydrate: function (element, container, callback) {
    // API,  render
    return renderSubtreeIntoContainer(null, element, container, true, callback);
  },
  render: function (element, container, callback) {
    //React15   API,      
    return renderSubtreeIntoContainer(null, element, container, false, callback);
  },
  unstable_renderSubtreeIntoContainer: function (parentComponent, element, containerNode, callback) {
    //      ,  
    return renderSubtreeIntoContainer(parentComponent, element, containerNode, false, callback);
  }
}
私たちはrendersSubtree IntoContinerを見ます.これは内部APIです.
//by     ,   :370262116     React anujs

function renderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {

  var root = container._reactRootContainer;
  if (!root) {
    //               ,           
    var shouldHydrate = forceHydrate || shouldHydrateDueToLegacyHeuristic(container);
    // First clear any existing content.
    if (!shouldHydrate) {
      var warned = false;
      var rootSibling = void 0;
      while (rootSibling = container.lastChild) {
        container.removeChild(rootSibling);
      }
    }

    var newRoot = DOMRenderer.createContainer(container, shouldHydrate);
    //    HostRoot  , Fiber     
    root = container._reactRootContainer = newRoot;
    
    // Initial mount should not be batched.
    DOMRenderer.unbatchedUpdates(function () {
     // newRoot      
      DOMRenderer.updateContainer(children, newRoot, parentComponent, callback);
    });
  } else {
    // root      
    DOMRenderer.updateContainer(children, root, parentComponent, callback);
  }
  return DOMRenderer.getPublicRootInstance(root);
}
DOMreender.creat Continerはどうやってrootオブジェクトを作成しますか?
まず、DMreenderというオブジェクトは、react Reconcilerという方法で生成され、オブジェクトに一部のものを注ぎ込む必要があります.最後に一つの対象を作ると、中にはcreateContinerという方法があります.
// containerInfo  ReactDOM.render(
, containerInfo) , createContainer: function (containerInfo, hydrate) { return createFiberRoot(containerInfo, hydrate); },

再看createFiberRoot是怎么将一个真实DOM变成一个Fiber对象

//by     ,   :370262116     React anujs

function createFiberRoot(containerInfo, hydrate) {
  // Cyclic construction. This cheats the type system right now because
  // stateNode is any.
  var uninitializedFiber = createHostRootFiber();
  var root = {
    current: uninitializedFiber,
    containerInfo: containerInfo,
    pendingChildren: null,
    remainingExpirationTime: NoWork,
    isReadyForCommit: false,
    finishedWork: null,
    context: null,
    pendingContext: null,
    hydrate: hydrate,
    nextScheduledRoot: null
  };
  uninitializedFiber.stateNode = root;

  return root;
}

function createHostRootFiber() {
  var fiber = createFiber(HostRoot, null, NoContext);
  return fiber;
}

var createFiber = function (tag, key, internalContextTag) {
  return new FiberNode(tag, key, internalContextTag);
};


function FiberNode(tag, key, internalContextTag) {
  // Instance
  this.tag = tag;
  this.key = key;
  this.type = null;
  this.stateNode = null;

  // Fiber
  this['return'] = null;
  this.child = null;
  this.sibling = null;
  this.index = 0;

  this.ref = null;

  this.pendingProps = null;
  this.memoizedProps = null;
  this.updateQueue = null;
  this.memoizedState = null;

  this.internalContextTag = internalContextTag;

  // Effects
  this.effectTag = NoEffect;
  this.nextEffect = null;

  this.firstEffect = null;
  this.lastEffect = null;

  this.expirationTime = NoWork;

  this.alternate = null;


}
すべてのファイバーオブジェクトはファイバーノードの実例であり、様々なタイプがあります.
内部には多くの方法があります.ファイバーオブジェクトを生成します.
  • createFiber FroomElement(typeはクラス、無状態関数、要素ラベル名)
  • createFiber Frame Fragment(typeはReact.Fragment)
  • createFiber Froom Text(JSXでは文字列、数字として表現)
  • createFiber Froom Host Instance ForDeletion
  • createFiber FroomCall
  • createFiber From Return
  • createFiber FroomPortal
  • createFiber Root(ReactDOM.render用のルートノード)
  • createfiber Rootは一般的なオブジェクトを作成しました.その中には、current属性がfiberオブジェクトを参照しています.containerInfo属性は先ほどのDOMノードを参照しています.そして、fiberオブジェクトはstateNodeが先ほどの一般的なオブジェクトを参照しています.React 15では、stateNodeはコンポーネントのインスタンスまたは実際のDOMであるべきであり、単に整列のために、通常のオブジェクトを作成することができる.最後に普通のオブジェクトに戻ります.
    私たちはまずDOMreender.unbatch edUpdatesを見ないで、直接DOMreender.udateContinerを見ます.
    //children  ReactDOM      ,children        ,           DOM ,                ,          ,parentComponent       ,    null
     DOMRenderer.updateContainer(children, newRoot, parentComponent, callback);
    udateContinerのソースコードも簡単です.コンテキストオブジェクトを獲得して、それをcontextといいますか?それともpendingConttextといいますか?最後にscheduleTopLevelUpdateに落とします.
    //by     ,   :370262116     React anujs
    
     updateContainer: function (element, container, parentComponent, callback) {
          var current = container.current;//createFiberRoot    fiber  
          var context = getContextForSubtree(parentComponent);
          if (container.context === null) {
            container.context = context;
          } else {
            container.pendingContext = context;
          }
          //      children, newRoot, parentComponent, callback
          // newRoot.fiber, children, callback
          scheduleTopLevelUpdate(current, element, callback);
        },
    get Conttext ForSubtreeの実現
    //by     ,   :370262116     React anujs
    
    function getContextForSubtree(parentComponent) {
      if (!parentComponent) {
        return emptyObject_1;
      }
    
      var fiber = get(parentComponent);
      var parentContext = findCurrentUnmaskedContext(fiber);
      return isContextProvider(fiber) ? processChildContext(fiber, parentContext) : parentContext;
    }
    //isContextConsumer isContextProvider        ,
    //              
    function isContextConsumer(fiber) {
      return fiber.tag === ClassComponent && fiber.type.contextTypes != null;
    }
    //isContextProvider,         
    function isContextProvider(fiber) {
      return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;
    }
    
    function _processChildContext(currentContext) {
        var Component = this._currentElement.type;
        var inst = this._instance;
        var childContext;
        if (inst.getChildContext) {
           childContext = inst.getChildContext();
        }
        
        if (childContext) {
            return _assign({}, currentContext, childContext);
        }
        return currentContext;
    }
    
    function findCurrentUnmaskedContext(fiber) {
     
      var node = fiber;
      while (node.tag !== HostRoot) {
        if (isContextProvider(node)) {
          return node.stateNode.__reactInternalMemoizedMergedChildContext;
        }
        var parent = node['return'];
        node = parent;
      }
      return node.stateNode.context;
    }
    私たちのparent Componentは最初は存在しないので、空いているオブジェクトに戻ります.この空のオブジェクトは繰り返し使用されていますが、新しい空のオブジェクトは毎回返されません.これは良い最適化です.
    scheduleTopLevelUpdateはユーザーのフィードバックをudateオブジェクトにパッケージ化し、udateの対象はpartial Stateオブジェクトであり、それはReact 15のset Stateに相当する最初のstate転送です.しかし、今はpartial Stateにチルドレンを入れました.
    //by     ,   :370262116     React anujs
    
    function scheduleTopLevelUpdate(current, element, callback) {
        // // newRoot.fiber, children, callback
    
        callback = callback === undefined ? null : callback;
        var expirationTime = void 0;
        // Check if the top-level element is an async wrapper component. If so,
        // treat updates to the root as async. This is a bit weird but lets us
        // avoid a separate `renderAsync` API.
        if (enableAsyncSubtreeAPI && element != null && element.type != null && element.type.prototype != null && element.type.prototype.unstable_isAsyncReactComponent === true) {
          expirationTime = computeAsyncExpiration();
        } else {
          expirationTime = computeExpirationForFiber(current);//      
        }
    
        var update = {
          expirationTime: expirationTime,//    
          partialState: { element: element },//!!!!  
          callback: callback,
          isReplace: false,
          isForced: false,
          nextCallback: null,
          next: null
        };
        insertUpdateIntoFiber(current, update);//      
        scheduleWork(current, expirationTime);//    
      }
    列はチェーンです.
    //by     ,   :370262116     React anujs
    // https://github.com/RubyLouvre/anu    star
    
    function insertUpdateIntoFiber(fiber, update) {
      // We'll have at least one and at most two distinct update queues.
      var alternateFiber = fiber.alternate;
      var queue1 = fiber.updateQueue;
      if (queue1 === null) {
        // TODO: We don't know what the base state will be until we begin work.
        // It depends on which fiber is the next current. Initialize with an empty
        // base state, then set to the memoizedState when rendering. Not super
        // happy with this approach.
        queue1 = fiber.updateQueue = createUpdateQueue(null);
      }
    
      var queue2 = void 0;
      if (alternateFiber !== null) {
        queue2 = alternateFiber.updateQueue;
        if (queue2 === null) {
          queue2 = alternateFiber.updateQueue = createUpdateQueue(null);
        }
      } else {
        queue2 = null;
      }
      queue2 = queue2 !== queue1 ? queue2 : null;
    
      // If there's only one queue, add the update to that queue and exit.
      if (queue2 === null) {
        insertUpdateIntoQueue(queue1, update);
        return;
      }
    
      // If either queue is empty, we need to add to both queues.
      if (queue1.last === null || queue2.last === null) {
        insertUpdateIntoQueue(queue1, update);
        insertUpdateIntoQueue(queue2, update);
        return;
      }
    
      // If both lists are not empty, the last update is the same for both lists
      // because of structural sharing. So, we should only append to one of
      // the lists.
      insertUpdateIntoQueue(queue1, update);
      // But we still need to update the `last` pointer of queue2.
      queue2.last = update;
    }
    
    function insertUpdateIntoQueue(queue, update) {
      // Append the update to the end of the list.
      if (queue.last === null) {
        // Queue is empty
        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;
      }
    }
    scheduleWorkは、仮想DOM(fiberツリー)の更新を実行するものである.scheduleWork,requestWork,performWorkは三部作です.
    //by     ,   :370262116     React anujs
    
    function scheduleWork(fiber, expirationTime) {
        return scheduleWorkImpl(fiber, expirationTime, false);
      }
    
      function checkRootNeedsClearing(root, fiber, expirationTime) {
        if (!isWorking && root === nextRoot && expirationTime < nextRenderExpirationTime) {
          // Restart the root from the top.
          if (nextUnitOfWork !== null) {
            // This is an interruption. (Used for performance tracking.)
            interruptedBy = fiber;
          }
          nextRoot = null;
          nextUnitOfWork = null;
          nextRenderExpirationTime = NoWork;
        }
      }
    
      function scheduleWorkImpl(fiber, expirationTime, isErrorRecovery) {
        recordScheduleUpdate();
    
    
        var node = fiber;
        while (node !== null) {
          // Walk the parent path to the root and update each node's
          // expiration time.
          if (node.expirationTime === NoWork || node.expirationTime > expirationTime) {
            node.expirationTime = expirationTime;
          }
          if (node.alternate !== null) {
            if (node.alternate.expirationTime === NoWork || node.alternate.expirationTime > expirationTime) {
              node.alternate.expirationTime = expirationTime;
            }
          }
          if (node['return'] === null) {
            if (node.tag === HostRoot) {
              var root = node.stateNode;
    
              checkRootNeedsClearing(root, fiber, expirationTime);
              requestWork(root, expirationTime);
              checkRootNeedsClearing(root, fiber, expirationTime);
            } else {
    
              return;
            }
          }
          node = node['return'];
        }
      }
    
    
    function requestWork(root, expirationTime) {
        if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
          invariant_1(false, 'Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.');
        }
    
        // Add the root to the schedule.
        // Check if this root is already part of the schedule.
        if (root.nextScheduledRoot === null) {
          // This root is not already scheduled. Add it.
          root.remainingExpirationTime = expirationTime;
          if (lastScheduledRoot === null) {
            firstScheduledRoot = lastScheduledRoot = root;
            root.nextScheduledRoot = root;
          } else {
            lastScheduledRoot.nextScheduledRoot = root;
            lastScheduledRoot = root;
            lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
          }
        } else {
          // This root is already scheduled, but its priority may have increased.
          var remainingExpirationTime = root.remainingExpirationTime;
          if (remainingExpirationTime === NoWork || expirationTime < remainingExpirationTime) {
            // Update the priority.
            root.remainingExpirationTime = expirationTime;
          }
        }
    
        if (isRendering) {
          // Prevent reentrancy. Remaining work will be scheduled at the end of
          // the currently rendering batch.
          return;
        }
    
        if (isBatchingUpdates) {
          // Flush work at the end of the batch.
          if (isUnbatchingUpdates) {
            // unless we're inside unbatchedUpdates, in which case we should
            // flush it now.
            nextFlushedRoot = root;
            nextFlushedExpirationTime = Sync;
            performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime);
          }
          return;
        }
    
        // TODO: Get rid of Sync and use current time?
        if (expirationTime === Sync) {
          performWork(Sync, null);
        } else {
          scheduleCallbackWithExpiration(expirationTime);
        }
      }
    
     function performWork(minExpirationTime, dl) {
        deadline = dl;
    
        // Keep working on roots until there's no more work, or until the we reach
        // the deadline.
        findHighestPriorityRoot();
    
        if (enableUserTimingAPI && deadline !== null) {
          var didExpire = nextFlushedExpirationTime < recalculateCurrentTime();
          stopRequestCallbackTimer(didExpire);
        }
    
        while (nextFlushedRoot !== null && nextFlushedExpirationTime !== NoWork && (minExpirationTime === NoWork || nextFlushedExpirationTime <= minExpirationTime) && !deadlineDidExpire) {
          performWorkOnRoot(nextFlushedRoot, nextFlushedExpirationTime);
          // Find the next highest priority work.
          findHighestPriorityRoot();
        }
    
        // We're done flushing work. Either we ran out of time in this callback,
        // or there's no more work left with sufficient priority.
    
        // If we're inside a callback, set this to false since we just completed it.
        if (deadline !== null) {
          callbackExpirationTime = NoWork;
          callbackID = -1;
        }
        // If there's work left over, schedule a new callback.
        if (nextFlushedExpirationTime !== NoWork) {
          scheduleCallbackWithExpiration(nextFlushedExpirationTime);
        }
    
        // Clean-up.
        deadline = null;
        deadlineDidExpire = false;
        nestedUpdateCount = 0;
    
        if (hasUnhandledError) {
          var _error4 = unhandledError;
          unhandledError = null;
          hasUnhandledError = false;
          throw _error4;
        }
      }
    
    function performWorkOnRoot(root, expirationTime) {
        !!isRendering ? invariant_1(false, 'performWorkOnRoot was called recursively. This error is likely caused by a bug in React. Please file an issue.') : void 0;
    
        isRendering = true;
    
        // Check if this is async work or sync/expired work.
        // TODO: Pass current time as argument to renderRoot, commitRoot
        if (expirationTime <= recalculateCurrentTime()) {
          // Flush sync work.
          var finishedWork = root.finishedWork;
          if (finishedWork !== null) {
            // This root is already complete. We can commit it.
            root.finishedWork = null;
            root.remainingExpirationTime = commitRoot(finishedWork);
          } else {
            root.finishedWork = null;
            finishedWork = renderRoot(root, expirationTime);
            if (finishedWork !== null) {
              // We've completed the root. Commit it.
              root.remainingExpirationTime = commitRoot(finishedWork);
            }
          }
        } else {
          // Flush async work.
          var _finishedWork = root.finishedWork;
          if (_finishedWork !== null) {
            // This root is already complete. We can commit it.
            root.finishedWork = null;
            root.remainingExpirationTime = commitRoot(_finishedWork);
          } else {
            root.finishedWork = null;
            _finishedWork = renderRoot(root, expirationTime);
            if (_finishedWork !== null) {
              // We've completed the root. Check the deadline one more time
              // before committing.
              if (!shouldYield()) {
                // Still time left. Commit the root.
                root.remainingExpirationTime = commitRoot(_finishedWork);
              } else {
                // There's no time left. Mark this root as complete. We'll come
                // back and commit it later.
                root.finishedWork = _finishedWork;
              }
            }
          }
        }
    
       isRendering = false;
    }
    //        ,          
    function findHighestPriorityRoot() {
        var highestPriorityWork = NoWork;
        var highestPriorityRoot = null;
    
        if (lastScheduledRoot !== null) {
          var previousScheduledRoot = lastScheduledRoot;
          var root = firstScheduledRoot;
          while (root !== null) {
            var remainingExpirationTime = root.remainingExpirationTime;
            if (remainingExpirationTime === NoWork) {
              // This root no longer has work. Remove it from the scheduler.
    
              // TODO: This check is redudant, but Flow is confused by the branch
              // below where we set lastScheduledRoot to null, even though we break
              // from the loop right after.
              !(previousScheduledRoot !== null && lastScheduledRoot !== null) ? invariant_1(false, 'Should have a previous and last root. This error is likely caused by a bug in React. Please file an issue.') : void 0;
              if (root === root.nextScheduledRoot) {
                // This is the only root in the list.
                root.nextScheduledRoot = null;
                firstScheduledRoot = lastScheduledRoot = null;
                break;
              } else if (root === firstScheduledRoot) {
                // This is the first root in the list.
                var next = root.nextScheduledRoot;
                firstScheduledRoot = next;
                lastScheduledRoot.nextScheduledRoot = next;
                root.nextScheduledRoot = null;
              } else if (root === lastScheduledRoot) {
                // This is the last root in the list.
                lastScheduledRoot = previousScheduledRoot;
                lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
                root.nextScheduledRoot = null;
                break;
              } else {
                previousScheduledRoot.nextScheduledRoot = root.nextScheduledRoot;
                root.nextScheduledRoot = null;
              }
              root = previousScheduledRoot.nextScheduledRoot;
            } else {
              if (highestPriorityWork === NoWork || remainingExpirationTime < highestPriorityWork) {
                // Update the priority, if it's higher
                highestPriorityWork = remainingExpirationTime;
                highestPriorityRoot = root;
              }
              if (root === lastScheduledRoot) {
                break;
              }
              previousScheduledRoot = root;
              root = root.nextScheduledRoot;
            }
          }
        }
    
        // If the next root is the same as the previous root, this is a nested
        // update. To prevent an infinite loop, increment the nested update count.
        var previousFlushedRoot = nextFlushedRoot;
        if (previousFlushedRoot !== null && previousFlushedRoot === highestPriorityRoot) {
          nestedUpdateCount++;
        } else {
          // Reset whenever we switch roots.
          nestedUpdateCount = 0;
        }
        nextFlushedRoot = highestPriorityRoot;
        nextFlushedExpirationTime = highestPriorityWork;
      }
    これはただ一部の更新ロジックで、もうきりがないです.今度続けて、フローチャートを追加して、本文で習ったことを思い出してください.