React 6.2のfiberアーキテクチャ
20615 ワード
React 16は本当に一日ごとに変わります.今見ないと、後で分かりにくくなります.
React 16では、JSXコンパイルによって一つの仮想DOMオブジェクトが得られたが、これらの仮想DOMオブジェクトの再加工は、天地を覆すような変化を経ている.私たちは底を追求して、どうやって一歩ずつ変えてきたのかを見ます.私たちはまずコンポーネントを見ません.まずReactDOM.renderを見つけます.ReactDOMのソースコードには、似たようなものが三つあります.
まず、DMreenderというオブジェクトは、react Reconcilerという方法で生成され、オブジェクトに一部のものを注ぎ込む必要があります.最後に一つの対象を作ると、中にはcreateContinerという方法があります.
内部には多くの方法があります.ファイバーオブジェクトを生成します. 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を見ます.
scheduleTopLevelUpdateはユーザーのフィードバックをudateオブジェクトにパッケージ化し、udateの対象はpartial Stateオブジェクトであり、それはReact 15のset Stateに相当する最初のstate転送です.しかし、今はpartial Stateにチルドレンを入れました.
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;
}
すべてのファイバーオブジェクトはファイバーノードの実例であり、様々なタイプがあります.内部には多くの方法があります.ファイバーオブジェクトを生成します.
私たちはまず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;
}
これはただ一部の更新ロジックで、もうきりがないです.今度続けて、フローチャートを追加して、本文で習ったことを思い出してください.