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, 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);
}
}
我们看renderSubtreeIntoContainer,这是一个内部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);
}
看一下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, 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;
}
所有Fiber对象都是FiberNode的实例,它有许多种类型,通过tag来标识。
内部有许多方法来生成Fiber对象
- createFiberFromElement (type为类,无状态函数,元素标签名)
- createFiberFromFragment (type为React.Fragment)
- createFiberFromText (在JSX中表现为字符串,数字)
- createFiberFromHostInstanceForDeletion
- createFiberFromCall
- createFiberFromReturn
- createFiberFromPortal (createPortal就会产生该类型)
- createFiberRoot (用于ReactDOM.render的根节点)
createFiberRoot就是创建了一个普通对象,里面有一个current属性引用fiber对象,有一个containerInfo属性引用刚才的DOM节点,然后fiber对象有一个stateNode引用刚才的普通对象。在React15中,stateNode应该是一个组件实例或真实DOM,可能单纯是为了对齐,就创建一个普通对象。 最后返回普通对象。
我们先不看 DOMRenderer.unbatchedUpdates,直接看DOMRenderer.updateContainer。
//children就是ReactDOM的第一个参数,children通常表示一个数组,但是现在它泛指各种虚拟DOM了,第二个对象就是刚才提到的普通对象,我们可以称它为根组件,parentComponent为之前的根组件,现在它为null
DOMRenderer.updateContainer(children, newRoot, parentComponent, callback);
updateContainer的源码也很简单,就是获得上下文对象,决定它是叫context还是pendingContext,最后丢给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);
},
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, 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;
}
这只是一部分更新逻辑, 简直没完没了,下次继续,添上流程图,回忆一下本文学到的东西
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。