react源码解析-jsx转换为ast树及渲染挂载
我用react有一段时间了,但是却对其实现的原理一知半解,只知道这样用能行,但是其怎么实现的并不知道。知其然才能对一些性能优化,写代码的时候才能考虑得更充分,将react的利用率达到更高,对代码的质量及项目的架构也会有更好的认识。
jsx转换ast树
-
babel识别jsx语法
babel在这里具体怎么将jsx转换为react的createElement函数调用,暂且不深说,只需要知道babel在这里的功能是将jsx语法的代码转换为React.createElement()的函数调用
在https://www.babeljs.cn/repl,可以测试:例:const a=<h1 style={{"color":"red"}}>123<div>test</div></h1>
经过编译之后
"use strict"; var a = React.createElement("h1", { style: { "color": "red" } }, "123", React.createElement("div", null, "test"));
可以看出babel在我们正常的项目中,遇到jsx语法就会将jsx代码转换为React.createElement()的函数调用
-
React.createElement()是如何转换为ast树的?
为了精确的调试和环境的纯净,这里我采用cdn引入react和react-dom的方式来进行测试。ReactDOM.render( React.createElement( 'div', {className:'red'}, 'Click Me' ), document.getElementById("root") )
最后ast树的样子
{ $$typeof: Symbol(react.element) key: null props: {className: "red", children: "Click Me"} ref: null type: "div" _owner: null _store: {validated: false} _self: null _source: null }
createElement()函数接收参数,并将参数读取转换为ast树的一些所需参数字段,目前我们的数据是比较简洁的,关注点在于props和type就好了
function createElement(type, config, children) { //根据上面的示例代码,type=div,config= {className:'red'},children='Click Me' var propName; // Reserved names are extracted debugger; var props = {};// 我们常用的props 目前组件 var key = null;//该组件的唯一key var ref = null;// 我们的ref var self = null; var source = null; if (config != null) { if (hasValidRef(config)) { ref = config.ref; } if (hasValidKey(config)) { key = '' + config.key; } self = config.__self === undefined ? null : config.__self; source = config.__source === undefined ? null : config.__source; // Remaining properties are added to a new props object for (propName in config) { if (hasOwnProperty$1.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)) { props[propName] = config[propName]; } } } // Children can be more than one argument, and those are transferred onto // the newly allocated props object. // 当发现arguments的参数大于1的时候。说明是有多个兄弟子元素的,如果等于1的话说明只有一个元素 var childrenLength = arguments.length - 2; if (childrenLength === 1) { // 直接将props的children设为当前children props.children = children; } else if (childrenLength > 1) { var childArray = Array(childrenLength); for (var i = 0; i < childrenLength; i++) { childArray[i] = arguments[i + 2]; } { if (Object.freeze) { Object.freeze(childArray); } } // 有多个兄弟元素的话,将兄弟节点放置在一个数组里面,赋值给props的children props.children = childArray; } // Resolve default props if (type && type.defaultProps) { var defaultProps = type.defaultProps; for (propName in defaultProps) { if (props[propName] === undefined) { props[propName] = defaultProps[propName]; } } } { if (key || ref) { var displayName = typeof type === 'function' ? type.displayName || type.name || 'Unknown' : type; if (key) { defineKeyPropWarningGetter(props, displayName); } if (ref) { defineRefPropWarningGetter(props, displayName); } } } // ReactElement 返回回来的是我们最终的ast树的结构 return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props); }
有多个兄弟元素的节点情况
"use strict"; // babel 编译过后生成的代码 var a = React.createElement("h1", { style: { "color": "red" } }, "123", React.createElement("div", null, "test")); // 经过React.createElement()函数调用之后得到的结果 {$$typeof: Symbol(react.element) key: null props: children: Array(2) 0: "Click Me" 1: $$typeof: Symbol(react.element) key: null props: {className: "red", children: "text"} ref: null type: "p" _owner: null _store: {validated: true} _self: null _source: null __proto__: Object length: 2 __proto__: Array(0) className: "red" __proto__: Object ref: null type: "div" _owner: null _store: {validated: false} _self: null _source: null }
渲染挂载
调用render方法-渲染
- render方法主要具备两个功能:
- 将ast树转换为真实的dom
- 挂载到页面上
ReactDOM.render(
React.createElement(
'div',
{className:'red'},
'Click Me',
React.createElement(
'p',
{className:'red'},
'text'
),
React.createElement(
'p',
{className:'red'},
'text'
)),
document.getElementById("root")
)
render方法中判断是否可用的dom元素及一些错误判断,此处可以不管。render函数最后调用的是legacyRenderSubtreeIntoContainer(),具体函数实现如下:
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
{
topLevelUpdateWarnings(container);
warnOnInvalidCallback(callback === undefined ? null : callback, 'render');
} // TODO: Without `any` type, Flow says "Property cannot be accessed on any
// member of intersection type." Whyyyyyy.
var root = container._reactRootContainer;
var fiberRoot;
if (!root) {
// Initial mount
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(container, forceHydrate);
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
var originalCallback = callback;
callback = function () {
var instance = getPublicRootInstance(fiberRoot);
originalCallback.call(instance);
};
} // Initial mount should not be batched.
unbatchedUpdates(function () {
// 挂载关注这个函数从这个函数去,会有ast转换为dom树的逻辑和如何插入到dom元素的逻辑
updateContainer(children, fiberRoot, parentComponent, callback);
});
} else {
fiberRoot = root._internalRoot;
if (typeof callback === 'function') {
var _originalCallback = callback;
callback = function () {
var instance = getPublicRootInstance(fiberRoot);
_originalCallback.call(instance);
};
} // Update
updateContainer(children, fiberRoot, parentComponent, callback);
}
return getPublicRootInstance(fiberRoot);
}
updateContainer()接收的参数有四个,children(之前react转换的ast树), fiberRoot(经过转换的根), parentComponent(父组件,当前没有), callback(渲染完成之后的回调),该函数的具体实现:
function updateContainer(element, container, parentComponent, callback) {
var current$$1 = container.current;
var currentTime = requestCurrentTime();
{
// $FlowExpectedError - jest isn't a global, and isn't recognized outside of tests
if ('undefined' !== typeof jest) {
warnIfUnmockedScheduler(current$$1);
warnIfNotScopedWithMatchingAct(current$$1);
}
}
var suspenseConfig = requestCurrentSuspenseConfig();
var expirationTime = computeExpirationForFiber(currentTime, current$$1, suspenseConfig);
// 这里和目前所研讨的主题关系不大,
return updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, suspenseConfig, callback);
}
继续进入updateContainerAtExpirationTime函数,按照我的理解。这个函数是为创建更新队列函数做准备,函数翻译过来是在时间线上更新容器
function updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, suspenseConfig, callback) {
// TODO: If this is a nested container, this won't be the root.
var current$$1 = container.current;
{
if (ReactFiberInstrumentation_1.debugTool) {
if (current$$1.alternate === null) {
ReactFiberInstrumentation_1.debugTool.onMountContainer(container);
} else if (element === null) {
ReactFiberInstrumentation_1.debugTool.onUnmountContainer(container);
} else {
ReactFiberInstrumentation_1.debugTool.onUpdateContainer(container);
}
}
}
var context = getContextForSubtree(parentComponent);
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
return scheduleRootUpdate(current$$1, element, expirationTime, suspenseConfig, callback);
}
继续进入scheduleRootUpdate,翻译过来是根据时间线更新根root
这个函数创建了一个更新的队列,然后开始时间线的工作
function scheduleRootUpdate(current$$1, element, expirationTime, suspenseConfig, callback) {
{
if (phase === 'render' && current !== null && !didWarnAboutNestedUpdates) {
didWarnAboutNestedUpdates = true;
warningWithoutStack$1(false, 'Render methods should be a pure function of props and state; ' + 'triggering nested component updates from render is not allowed. ' + 'If necessary, trigger nested updates in componentDidUpdate.\n\n' + 'Check the render method of %s.', getComponentName(current.type) || 'Unknown');
}
}
var update = createUpdate(expirationTime, suspenseConfig); // Caution: React DevTools currently depends on this property
// being called "element".
update.payload = {
element: element
};
callback = callback === undefined ? null : callback;
if (callback !== null) {
!(typeof callback === 'function') ? warningWithoutStack$1(false, 'render(...): Expected the last optional `callback` argument to be a ' + 'function. Instead received: %s.', callback) : void 0;
update.callback = callback;
}
enqueueUpdate(current$$1, update);
// 工作的开启时这个函数,这个函数
scheduleWork(current$$1, expirationTime);
return expirationTime;
}
scheduleWork时等于scheduleUpdateOnFiber的翻译过来是在fiber(根)上的按照时间更新,单步调试执行会到performSyncWorkOnRoot()这个函数。
function scheduleUpdateOnFiber(fiber, expirationTime) {
// fiber 是contarner.current,
debugger;
checkForNestedUpdates();
warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);
var root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);
if (root === null) {
warnAboutUpdateOnUnmountedFiberInDEV(fiber);
return;
}
checkForInterruption(fiber, expirationTime);
recordScheduleUpdate(); // TODO: computeExpirationForFiber also reads the priority. Pass the
// priority as an argument to that function and this one.
var priorityLevel = getCurrentPriorityLevel();
if (expirationTime === Sync) {
if ( // Check if we're inside unbatchedUpdates
(executionContext & LegacyUnbatchedContext) !== NoContext && // Check if we're not already rendering
(executionContext & (RenderContext | CommitContext)) === NoContext) {
// Register pending interactions on the root to avoid losing traced interaction data.
schedulePendingInteractions(root, expirationTime); // This is a legacy edge case. The initial mount of a ReactDOM.render-ed
// root inside of batchedUpdates should be synchronous, but layout updates
// should be deferred until the end of the batch.
// 单步调试会到这里
performSyncWorkOnRoot(root);
} else {
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
if (executionContext === NoContext) {
// Flush the synchronous work now, unless we're already working or inside
// a batch. This is intentionally inside scheduleUpdateOnFiber instead of
// scheduleCallbackForFiber to preserve the ability to schedule a callback
// without immediately flushing it. We only do this for user-initiated
// updates, to preserve historical behavior of sync mode.
flushSyncCallbackQueue();
}
}
} else {
ensureRootIsScheduled(root);
schedulePendingInteractions(root, expirationTime);
}
if ((executionContext & DiscreteEventContext) !== NoContext && ( // Only updates at user-blocking priority or greater are considered
// discrete, even inside a discrete event.
priorityLevel === UserBlockingPriority$2 || priorityLevel === ImmediatePriority)) {
// This is the result of a discrete event. Track the lowest priority
// discrete update per root so we can flush them early, if needed.
if (rootsWithPendingDiscreteUpdates === null) {
rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);
} else {
var lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);
if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {
rootsWithPendingDiscreteUpdates.set(root, expirationTime);
}
}
}
}
var scheduleWork = scheduleUpdateOnFiber; // This is split into a separate function so we can mark a fiber with pending
performSyncWorkOnRoot(),在root上执行异步的工作,也就是执行ast树转换为真实dom的逻辑,单步调试,向下执行。会到一个循环workLoopSync()翻译过来是循环异步工作,因为我们的ast是树,然后当前传入的ast是多个react,ast树,所以需要循环。
function performSyncWorkOnRoot(root) {
// Check if there's expired work on this root. Otherwise, render at Sync.
var lastExpiredTime = root.lastExpiredTime;
var expirationTime = lastExpiredTime !== NoWork ? lastExpiredTime : Sync;
console.log(root)
if (root.finishedExpirationTime === expirationTime) {
// There's already a pending commit at this expiration time.
// TODO: This is poorly factored. This case only exists for the
// batch.commit() API.
commitRoot(root);
} else {
(function () {
if (!((executionContext & (RenderContext | CommitContext)) === NoContext)) {
{
throw ReactError(Error("Should not already be working."));
}
}
})();
flushPassiveEffects(); // If the root or expiration time have changed, throw out the existing stack
// and prepare a fresh one. Otherwise we'll continue where we left off.
console.log(root)
if (root !== workInProgressRoot || expirationTime !== renderExpirationTime) {
prepareFreshStack(root, expirationTime);
startWorkOnPendingInteractions(root, expirationTime);
} // If we have a work-in-progress fiber, it means there's still work to do
// in this root.
if (workInProgress !== null) {
var prevExecutionContext = executionContext;
executionContext |= RenderContext;
var prevDispatcher = pushDispatcher(root);
var prevInteractions = pushInteractions(root);
startWorkLoopTimer(workInProgress);
debugger;
do {
try {
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
resetContextDependencies();
executionContext = prevExecutionContext;
popDispatcher(prevDispatcher);
if (enableSchedulerTracing) {
popInteractions(prevInteractions);
}
if (workInProgressRootExitStatus === RootFatalErrored) {
var fatalError = workInProgressRootFatalError;
stopInterruptedWorkLoopTimer();
prepareFreshStack(root, expirationTime);
markRootSuspendedAtTime(root, expirationTime);
ensureRootIsScheduled(root);
throw fatalError;
}
if (workInProgress !== null) {
// This is a sync render, so we should have finished the whole tree.
(function () {
{
{
throw ReactError(Error("Cannot commit an incomplete root. This error is likely caused by a bug in React. Please file an issue."));
}
}
})();
} else {
// We now have a consistent tree. Because this is a sync render, we
// will commit it even if something suspended. The only exception is
// if the root is locked (using the unstable_createBatch API).
stopFinishedWorkLoopTimer();
root.finishedWork = root.current.alternate;
root.finishedExpirationTime = expirationTime;
resolveLocksOnRoot(root, expirationTime);
finishSyncRender(root, workInProgressRootExitStatus, expirationTime);
} // Before exiting, make sure there's a callback scheduled for the next
// pending level.
ensureRootIsScheduled(root);
}
}
return null;
}
workLoopSync函数:
function workLoopSync() {
debugger;
// Already timed out, so perform work without checking if we need to yield.
// 判断 workInProgress 为null的时候就执行完毕了。
// 第一次进来的时候:workInProgress =其实是root.current.alternate
while (workInProgress !== null) {
workInProgress = performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork) {
// The current, flushed, state of this fiber is the alternate. Ideally
// nothing should rely on this, but relying on it here means that we don't
// need an additional field on the work in progress.
var current$$1 = unitOfWork.alternate;
startWorkTimer(unitOfWork);
setCurrentFiber(unitOfWork);
var next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork$$1(current$$1, unitOfWork, renderExpirationTime);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
// next是去查找下一个工作单元,
next = beginWork$$1(current$$1, unitOfWork, renderExpirationTime);
}
resetCurrentFiber();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
// 判断下一个工作单元为空的话。完成当前单元工作
if (next === null) {
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(unitOfWork);
}
// 此处返回的是workInProgress的值,会再次执行下来。
ReactCurrentOwner$2.current = null;
return next;
}
function completeUnitOfWork(unitOfWork) {
// Attempt to complete the current unit of work, then move to the next
// sibling. If there are no more siblings, return to the parent fiber.
// 将工作树赋值为当前单元工作树,
workInProgress = unitOfWork;
do {
// The current, flushed, state of this fiber is the alternate. Ideally
// nothing should rely on this, but relying on it here means that we don't
// need an additional field on the work in progress.
var current$$1 = workInProgress.alternate;
var returnFiber = workInProgress.return; // Check if the work completed or if something threw.
if ((workInProgress.effectTag & Incomplete) === NoEffect) {
setCurrentFiber(workInProgress);
var next = void 0;
if (!enableProfilerTimer || (workInProgress.mode & ProfileMode) === NoMode) {
next = completeWork(current$$1, workInProgress, renderExpirationTime);
} else {
startProfilerTimer(workInProgress);
// 主要看这个函数,具体完成元素渲染操作的。
next = completeWork(current$$1, workInProgress, renderExpirationTime); // Update render duration assuming we didn't error.
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false);
}
stopWorkTimer(workInProgress);
resetCurrentFiber();
resetChildExpirationTime(workInProgress);
if (next !== null) {
// Completing this fiber spawned new work. Work on that next.
return next;
}
debugger;
if (returnFiber !== null && // Do not append effects to parents if a sibling failed to complete
(returnFiber.effectTag & Incomplete) === NoEffect) {
// Append all the effects of the subtree and this fiber onto the effect
// list of the parent. The completion order of the children affects the
// side-effect order.
if (returnFiber.firstEffect === null) {
returnFiber.firstEffect = workInProgress.firstEffect;
}
if (workInProgress.lastEffect !== null) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress.firstEffect;
}
returnFiber.lastEffect = workInProgress.lastEffect;
} // If this fiber had side-effects, we append it AFTER the children's
// side-effects. We can perform certain side-effects earlier if needed,
// by doing multiple passes over the effect list. We don't want to
// schedule our own side-effect on our own list because if end up
// reusing children we'll schedule this effect onto itself since we're
// at the end.
var effectTag = workInProgress.effectTag; // Skip both NoWork and PerformedWork tags when creating the effect
// list. PerformedWork effect is read by React DevTools but shouldn't be
// committed.
if (effectTag > PerformedWork) {
if (returnFiber.lastEffect !== null) {
returnFiber.lastEffect.nextEffect = workInProgress;
} else {
returnFiber.firstEffect = workInProgress;
}
returnFiber.lastEffect = workInProgress;
}
}
} else {
// This fiber did not complete because something threw. Pop values off
// the stack without entering the complete phase. If this is a boundary,
// capture values if possible.
var _next = unwindWork(workInProgress, renderExpirationTime); // Because this fiber did not complete, don't reset its expiration time.
if (enableProfilerTimer && (workInProgress.mode & ProfileMode) !== NoMode) {
// Record the render duration for the fiber that errored.
stopProfilerTimerIfRunningAndRecordDelta(workInProgress, false); // Include the time spent working on failed children before continuing.
var actualDuration = workInProgress.actualDuration;
var child = workInProgress.child;
while (child !== null) {
actualDuration += child.actualDuration;
child = child.sibling;
}
workInProgress.actualDuration = actualDuration;
}
if (_next !== null) {
// If completing this work spawned new work, do that next. We'll come
// back here again.
// Since we're restarting, remove anything that is not a host effect
// from the effect tag.
// TODO: The name stopFailedWorkTimer is misleading because Suspense
// also captures and restarts.
stopFailedWorkTimer(workInProgress);
_next.effectTag &= HostEffectMask;
return _next;
}
stopWorkTimer(workInProgress);
if (returnFiber !== null) {
// Mark the parent fiber as incomplete and clear its effect list.
returnFiber.firstEffect = returnFiber.lastEffect = null;
returnFiber.effectTag |= Incomplete;
}
}
var siblingFiber = workInProgress.sibling;
if (siblingFiber !== null) {
// If there is more work to do in this returnFiber, do that next.
return siblingFiber;
} // Otherwise, return to the parent
workInProgress = returnFiber;
} while (workInProgress !== null); // We've reached the root.
if (workInProgressRootExitStatus === RootIncomplete) {
workInProgressRootExitStatus = RootCompleted;
}
//函数执行到这里的话。就说明已经渲染完了。
return null;
}
conpleteWork函数,具体的作用就是去根据当前的workINProgress的tag渲染不同的内容,我们的一个节点是一个文本节点,这里是部分代码
function completeWork(current, workInProgress, renderExpirationTime) {
var newProps = workInProgress.pendingProps;
switch (workInProgress.tag) {
// 渲染文本节点的逻辑,
case HostText:
{
var newText = newProps;
if (current && workInProgress.stateNode != null) {
var oldText = current.memoizedProps; // If we have an alternate, that means this is an update and we need
// to schedule a side-effect to do the updates.
updateHostText$1(current, workInProgress, oldText, newText);
} else {
if (typeof newText !== 'string') {
(function () {
if (!(workInProgress.stateNode !== null)) {
{
throw ReactError(Error("We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue."));
}
}
})(); // This can happen when we abort work.
}
var _rootContainerInstance = getRootHostContainer();
var _currentHostContext = getHostContext();
var _wasHydrated2 = popHydrationState(workInProgress);
if (_wasHydrated2) {
if (prepareToHydrateHostTextInstance(workInProgress)) {
markUpdate(workInProgress);
}
} else {
// 这一个函数创建并且将文本dom节点赋值给当前 workInProgress.stateNode
workInProgress.stateNode = createTextInstance(newText, _rootContainerInstance, _currentHostContext, workInProgress);
console.log("创建了文字节点",workInProgress)
}
}
break;
}
createTextInstance函数
function createTextNode(text, rootContainerElement) {
// 运用原生document.创建函数。去创建一个文本节点,返回赋值给workInProgress.stateNode
return getOwnerDocumentFromRootContainer(rootContainerElement).createTextNode(text);
}
普通元素节点的创建函数是popHostContext
case HostComponent:
{
popHostContext(workInProgress);
var rootContainerInstance = getRootHostContainer();
var type = workInProgress.type;
if (current !== null && workInProgress.stateNode != null) {
updateHostComponent$1(current, workInProgress, type, newProps, rootContainerInstance);
if (enableFlareAPI) {
var prevListeners = current.memoizedProps.listeners;
var nextListeners = newProps.listeners;
if (prevListeners !== nextListeners) {
markUpdate(workInProgress);
}
}
if (current.ref !== workInProgress.ref) {
markRef$1(workInProgress);
}
} else {
if (!newProps) {
(function () {
if (!(workInProgress.stateNode !== null)) {
{
throw ReactError(Error("We must have new props for new mounts. This error is likely caused by a bug in React. Please file an issue."));
}
}
})(); // This can happen when we abort work.
break;
}
var currentHostContext = getHostContext(); // TODO: Move createInstance to beginWork and keep it on a context
// "stack" as the parent. Then append children as we go in beginWork
// or completeWork depending on we want to add then top->down or
// bottom->up. Top->down is faster in IE11.
var _wasHydrated = popHydrationState(workInProgress);
if (_wasHydrated) {
// TODO: Move this and createInstance step into the beginPhase
// to consolidate.
if (prepareToHydrateHostInstance(workInProgress, rootContainerInstance, currentHostContext)) {
// If changes to the hydrated node needs to be applied at the
// commit-phase we mark this as such.
markUpdate(workInProgress);
}
if (enableFlareAPI) {
var listeners = newProps.listeners;
if (listeners != null) {
updateEventListeners(listeners, workInProgress, rootContainerInstance);
}
}
} else {
// 他是一个元素节点,在这里创建一个元素标签,
var instance = createInstance(type, newProps, rootContainerInstance, currentHostContext, workInProgress);
// 把所有的孩子放到 instance的下面。建立关系,
appendAllChildren(instance, workInProgress, false, false); // This needs to be set before we mount Flare event listeners
workInProgress.stateNode = instance;
if (enableFlareAPI) {
var _listeners = newProps.listeners;
if (_listeners != null) {
updateEventListeners(_listeners, workInProgress, rootContainerInstance);
}
} // Certain renderers require commit-time effects for initial mount.
// (eg DOM renderer supports auto-focus for certain elements).
// Make sure such renderers get scheduled for later work.
if (finalizeInitialChildren(instance, type, newProps, rootContainerInstance, currentHostContext)) {
markUpdate(workInProgress);
}
}
if (workInProgress.ref !== null) {
// If there is a ref on a host node we need to schedule a callback
markRef$1(workInProgress);
}
}
break;
}
这个函数会根据 workInProgress.child里面的fiber结构树进行遍历组装真实的dom树,那么在什么时候为我们准备好的workInProgress.child的树的转换呢?
appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
var node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
appendInitialChild(parent, node.stateNode.instance);
} else if (node.tag === HostPortal) {// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
};
function performUnitOfWork(unitOfWork) {
// The current, flushed, state of this fiber is the alternate. Ideally
// nothing should rely on this, but relying on it here means that we don't
// need an additional field on the work in progress.
var current$$1 = unitOfWork.alternate;
startWorkTimer(unitOfWork);
setCurrentFiber(unitOfWork);
var next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork$$1(current$$1, unitOfWork, renderExpirationTime);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
// next是去查找下一个工作单元,
next = beginWork$$1(current$$1, unitOfWork, renderExpirationTime);
}
resetCurrentFiber();
unitOfWork.memoizedProps = unitOfWork.pendingProps;
// 判断下一个工作单元为空的话。完成当前单元工作
if (next === null) {
// If this doesn't spawn new work, complete the current work.
next = completeUnitOfWork(unitOfWork);
}
// 此处返回的是workInProgress的值,会再次执行下来。
ReactCurrentOwner$2.current = null;
return next;
}
在performUnitOfWork这个函数。每一次在准备下一个工作单元的时候,在beginWork$$1这个函数就会组装workInPregress.child,来看函数内部的实现
function beginWork$1(current$$1, workInProgress, renderExpirationTime) {
switch (workInProgress.tag) {
case HostRoot:
// 重点关注这个函数,是由他实现具体的转换操作
return updateHostRoot(current$$1, workInProgress, renderExpirationTime);
}
function updateHostRoot(current$$1, workInProgress, renderExpirationTime) {
// Otherwise reset hydration state in case we aborted and resumed another
// root.
// 重点关注这个函数。
reconcileChildren(current$$1, workInProgress, nextChildren, renderExpirationTime);
return workInProgress.child;
}
function reconcileChildren(current$$1, workInProgress, nextChildren, renderExpirationTime) {
if (current$$1 === null) {
// If this is a fresh new component that hasn't been rendered yet, we
// won't update its child set by applying minimal side-effects. Instead,
// we will add them all to the child before it gets rendered. That means
// we can optimize this reconciliation pass by not tracking side-effects.
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderExpirationTime);
} else {
// If the current child is the same as the work in progress, it means that
// we haven't yet started any work on these children. Therefore, we use
// the clone algorithm to create a copy of all the current children.
// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
// 在这里组装并赋值给当前的workINProgress
workInProgress.child = reconcileChildFibers(workInProgress, current$$1.child, nextChildren, renderExpirationTime);
}
}
第一次进来组装得到的child,为最外面的那一个div,第二次组装的是文本节点,click me,而当前的workInProgress.return.child中的结构就是div包住一个文本节点,她的sibing是p,p的sibing是p,整个需要挂载的元素的fiber结构元素就组装完毕了。到这里,
appendAllChildren = function (parent, workInProgress, needsVisibilityToggle, isHidden) {
// We only have the top Fiber that was created but we need recurse down its
// children to find all the terminal nodes.
var node = workInProgress.child;
while (node !== null) {
if (node.tag === HostComponent || node.tag === HostText) {
appendInitialChild(parent, node.stateNode);
} else if (enableFundamentalAPI && node.tag === FundamentalComponent) {
appendInitialChild(parent, node.stateNode.instance);
} else if (node.tag === HostPortal) {// If we have a portal child, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === workInProgress) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === workInProgress) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
//
node = node.sibling;
}
};
appendAllChildren当遇到节点是最外面的那个div的时候。就可以根据这个child,去循环遍历出最终需要插入到页面的stateNode,
挂载
挂载逻辑在得到渲染好的frber结构树,
在workLoopSync();继续执行下去会调用到这个函数
function finishSyncRender(root, exitStatus, expirationTime) {
if (exitStatus === RootLocked) {
// This root has a lock that prevents it from committing. Exit. If we
// begin work on the root again, without any intervening updates, it
// will finish without doing additional work.
markRootSuspendedAtTime(root, expirationTime);
} else {
// Set this to null to indicate there's no in-progress render.
workInProgressRoot = null;
{
if (exitStatus === RootSuspended || exitStatus === RootSuspendedWithDelay) {
flushSuspensePriorityWarningInDEV();
}
}
debugger;
// 这里有一些调度相关的逻辑,我们重点看react是怎么挂载到dom元素上的。
commitRoot(root);
}
}
// 省略部分代码
commitPlacement(){
var node = finishedWork;
while (true) {
var isHost = node.tag === HostComponent || node.tag === HostText;
if (isHost || enableFundamentalAPI && node.tag === FundamentalComponent) {
var stateNode = isHost ? node.stateNode : node.stateNode.instance;
if (before) {
if (isContainer) {
insertInContainerBefore(parent, stateNode, before);
} else {
insertBefore(parent, stateNode, before);
}
} else {
debugger;
if (isContainer) {
// 我们的示例会进入到这里
appendChildToContainer(parent, stateNode);
} else {
appendChild(parent, stateNode);
}
}
} else if (node.tag === HostPortal) {// If the insertion itself is a portal, then we don't want to traverse
// down its children. Instead, we'll get insertions from each child in
// the portal directly.
} else if (node.child !== null) {
node.child.return = node;
node = node.child;
continue;
}
if (node === finishedWork) {
return;
}
while (node.sibling === null) {
if (node.return === null || node.return === finishedWork) {
return;
}
node = node.return;
}
node.sibling.return = node.return;
node = node.sibling;
}
}
function appendChildToContainer(container, child) {
var parentNode;
debugger;
if (container.nodeType === COMMENT_NODE) {
parentNode = container.parentNode;
parentNode.insertBefore(child, container);
} else {
parentNode = container;
parentNode.appendChild(child);
} // This container might be used for a portal.
// If something inside a portal is clicked, that click should bubble
// through the React tree. However, on Mobile Safari the click would
// never bubble through the *DOM* tree unless an ancestor with onclick
// event exists. So we wouldn't see it and dispatch it.
// This is why we ensure that non React root containers have inline onclick
// defined.
// https://github.com/facebook/react/issues/11918
var reactRootContainer = container._reactRootContainer;
if ((reactRootContainer === null || reactRootContainer === undefined) && parentNode.onclick === null) {
// TODO: This cast may not be sound for SVG, MathML or custom elements.
trapClickOnNonInteractiveElement(parentNode);
}
}
在这里实际调用dom的添加元素节点的函数。实现了真正的挂载
总结
-
jsx语法糖的html结构是通过babel将结构解析转换为React.createElement函数调用。
-
React.createElement做的事情就是生成react的ast树
-
渲染,render函数主要做了两件事,一件是将ast树转换为fiber 结构。填入许多调度、更新、diff相关数据,并转换ast树为dom树,一件是挂载。
-
ast树转换为dom树,先是为我们准备好fiber版本的节点关系结构,然后从下往上的渲染一个一个的元素,最后将所有的节点添加到最外面的那一层dom上面,并赋值给最外层的fiber.stateNode,fiber.stateNode即将插入页面的dom元素。