React16.2的fiber架构
|
React16真是一天一改,如果现在不看,以后也很难看懂了。 在React16中,虽然也是通过JSX编译得到一个虚拟DOM对象,但对这些虚拟DOM对象的再加工则是经过翻天覆地的变化。我们需要追根溯底,看它是怎么一步步转换过来的。我们先不看什么组件render,先找到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,true,callback);
},render: function (element,callback) {
//React15的重要API,逐渐退出舞台
return renderSubtreeIntoContainer(null,false,unstable_renderSubtreeIntoContainer: function (parentComponent,containerNode,callback) {
//用于生成子树,废弃
return renderSubtreeIntoContainer(parentComponent,callback);
}
}
我们看renderSubtreeIntoContainer,这是一个内部API //by 司徒正美, 加群:370262116 一起研究React与anujs
function renderSubtreeIntoContainer(parentComponent,children,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,callback);
}
return DOMRenderer.getPublicRootInstance(root);
}
看一下DOMRenderer.createContainer是怎么创建root对象的。 首先DOMRenderer这个对象是由一个叫reactReconciler的方法生成,需要传入一个对象,将一些东西注进去。最后产生一个对象,里面就有createContainer这个方法 // containerInfo就是ReactDOM.render(<div/>,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,internalContextTag);
};
function FiberNode(tag,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;
}
所有Fiber对象都是FiberNode的实例,它有许多种类型,通过tag来标识。 内部有许多方法来生成Fiber对象
createFiberRoot就是创建了一个普通对象,里面有一个current属性引用fiber对象,有一个containerInfo属性引用刚才的DOM节点,然后fiber对象有一个stateNode引用刚才的普通对象。在React15中,stateNode应该是一个组件实例或真实DOM,可能单纯是为了对齐,就创建一个普通对象。 最后返回普通对象。 我们先不看 DOMRenderer.unbatchedUpdates,直接看DOMRenderer.updateContainer。 //children就是ReactDOM的第一个参数,children通常表示一个数组,但是现在它泛指各种虚拟DOM了,第二个对象就是刚才提到的普通对象,我们可以称它为根组件,parentComponent为之前的根组件,现在它为null DOMRenderer.updateContainer(children,callback); updateContainer的源码也很简单,就是获得上下文对象,决定它是叫context还是pendingContext,最后丢给scheduleTopLevelUpdate //by 司徒正美, 加群:370262116 一起研究React与anujs
updateContainer: function (element,callback) {
var current = container.current;//createFiberRoot中创建的fiber对象
var context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
// 原传名为 children,callback
// newRoot.fiber,callback
scheduleTopLevelUpdate(current,callback);
},
getContextForSubtree的实现 //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;
}
因为我们的parentComponent一开始不存在,于是返回一个空对象。注意,这个空对象是重复使用的,不是每次返回一个新的空对象,这是一个很好的优化。 scheduleTopLevelUpdate是将用户的传参封装成一个update对象,update对象有partialState对象,它就是相当于React15中 的setState的第一个state传参。但现在partialState中竟然把children放进去了。 //by 司徒正美, 加群:370262116 一起研究React与anujs
function scheduleTopLevelUpdate(current,callback) {
// // newRoot.fiber,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,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,expirationTime);
requestWork(root,expirationTime);
checkRootNeedsClearing(root,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;
}
这只是一部分更新逻辑, 简直没完没了,下次继续,添上流程图,回忆一下本文学到的东西
(编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |
