继续上边说道的将fiber传入了scheduleWork函数中,接着如何一步一步去进行渲染呢?
在scheduleWork内部会将fiber传入一个scheduleWorkToRoot进行加工。
在scheduleWorkToRoot中会对fiber的expirationTime进行赋值,然后返回fiber中的stateNode出来。之后会传入一个markPendingPriorityLevel的函数,但是我们暂时不关注它,因为它应该是做异步渲染的。之后就会将root和expirationTime传入到一个requestWork的函数中。
requestWork中会调用addRootToSchedule,最后会将root赋值到全局的firstScheduledRoot和lastScheduledRoot中。其实也是有很多异步渲染的工作会做,但是我们都先不关注它,直接看到performSyncWork这个函数中,因为我们都是以同步的方式进行工作,所以也只会进入这个函数中。
performSyncWork内部performWork。
performWork中立马会执行findHighestPriorityRoot这个函数,这个函数的目的就是将firstScheduledRoot赋值到nextFlushedRoot。
紧接着就会将nextFlushedRoot传入performWorkOnRoot当中。
首先会将全局的isRendering设置为true。之后就会开始执行 renderRoot函数了。其实在执行renderRoot之前,我们回顾一下我们的DEMO。
import React from 'react';
import ReactDOM from 'react-dom';
class H1 extends React.Component {
static defaultProps = {
text: 'DEMO'
}
render() {
return (<h1>{this.props.text}</h1>)
}
}
class App extends React.Component {
render() {
return (<H1/>)
}
}
ReactDOM.render(
<App/>,
document.getElementById('root'),
() => {
console.log(1, arguments);
}
)
这个时候我们查看一下我们的root对象中的App。
我们会发现App到现在还是没有实例化出来的。包括里面的其他组件都并没有实例化出来的。那么renderRoot就是将App里面的子组件还是各种props实例化成一个对象出来。
更换名称:nextRoot = root;
在renderRoot中,会先将nextRoot.current传入createWorkInProgress中,createWorkInProgress会将传入的current的内容生成一个Fiber对象,以下是createWorkInProgress的代码:
// This is used to create an alternate fiber to do work on.
function createWorkInProgress(current, pendingProps, expirationTime) {
var workInProgress = current.alternate;
if (workInProgress === null) {
// We use a double buffering pooling technique because we know that we'll
// only ever need at most two versions of a tree. We pool the "other" unused
// node that we're free to reuse. This is lazily created to avoid allocating
// extra objects for things that are never updated. It also allow us to
// reclaim the extra memory if needed.
workInProgress = createFiber(current.tag, pendingProps, current.key, current.mode);
workInProgress.elementType = current.elementType;
workInProgress.type = current.type;
workInProgress.stateNode = current.stateNode;
{
// DEV-only fields
workInProgress._debugID = current._debugID;
workInProgress._debugSource = current._debugSource;
workInProgress._debugOwner = current._debugOwner;
}
workInProgress.alternate = current;
current.alternate = workInProgress;
} else {
workInProgress.pendingProps = pendingProps;
// We already have an alternate.
// Reset the effect tag.
workInProgress.effectTag = NoEffect;
// The effect list is no longer valid.
workInProgress.nextEffect = null;
workInProgress.firstEffect = null;
workInProgress.lastEffect = null;
if (enableProfilerTimer) {
// We intentionally reset, rather than copy, actualDuration & actualStartTime.
// This prevents time from endlessly accumulating in new commits.
// This has the downside of resetting values for different priority renders,
// But works for yielding (the common case) and should support resuming.
workInProgress.actualDuration = 0;
workInProgress.actualStartTime = -1;
}
}
workInProgress.childExpirationTime = current.childExpirationTime;
workInProgress.expirationTime = current.expirationTime;
workInProgress.child = current.child;
workInProgress.memoizedProps = current.memoizedProps;
workInProgress.memoizedState = current.memoizedState;
workInProgress.updateQueue = current.updateQueue;
workInProgress.firstContextDependency = current.firstContextDependency;
// These will be overridden during the parent's reconciliation
workInProgress.sibling = current.sibling;
workInProgress.index = current.index;
workInProgress.ref = current.ref;
if (enableProfilerTimer) {
workInProgress.selfBaseDuration = current.selfBaseDuration;
workInProgress.treeBaseDuration = current.treeBaseDuration;
}
return workInProgress;
}
其实就是将当前传入的current的内容赋值给新的Fibre,然后返回出来赋值到全局的nextUnitOfWork上。
然后会将全局的nextUnitOfWork传入startWorkLoopTimer中, startWorkLoopTimer会将传入的nextUnitOfWork赋值到全局的currentFiber中。之后就开始执行workLoop了!!!
function workLoop(isYieldy) {
if (!isYieldy) {
// Flush work without yielding
while (nextUnitOfWork !== null) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
} else {
// Flush asynchronous work until there's a higher priority event
while (nextUnitOfWork !== null && !shouldYieldToRenderer()) {
nextUnitOfWork = performUnitOfWork(nextUnitOfWork);
}
}
}
默认的情况下,isYieldy是写死为false的,因为react还没有出正式版,并没有完成异步渲染。其实workLoop就是不停的循环,nextUnitOfWork直到全局的nextUnitOfWork为null为止。
修改命名:workInProgress == nextUnitOfWork
function updateHostRoot(current$$1, workInProgress, renderExpirationTime) {
pushHostRootContext(workInProgress);
var updateQueue = workInProgress.updateQueue;
!(updateQueue !== null) ? invariant(false, 'If the root does not have an updateQueue, we should have already bailed out. This error is likely caused by a bug in React. Please file an issue.') : void 0;
var nextProps = workInProgress.pendingProps;
var prevState = workInProgress.memoizedState;
var prevChildren = prevState !== null ? prevState.element : null;
// 设置workInProgress.memoizedState
processUpdateQueue(workInProgress, updateQueue, nextProps, null, renderExpirationTime);
var nextState = workInProgress.memoizedState;
// Caution: React DevTools currently depends on this property
// being called "element".
var nextChildren = nextState.element;
if (nextChildren === prevChildren) {
// If the state is the same as before, that's a bailout because we had
// no work that expires at this time.
resetHydrationState();
return bailoutOnAlreadyFinishedWork(current$$1, workInProgress, renderExpirationTime);
}
var root = workInProgress.stateNode;
if ((current$$1 === null || current$$1.child === null) && root.hydrate && enterHydrationState(workInProgress)) {
// If we don't have any current children this might be the first pass.
// We always try to hydrate. If this isn't a hydration pass there won't
// be any children to hydrate which is effectively the same thing as
// not hydrating.
// This is a bit of a hack. We track the host root as a placement to
// know that we're currently in a mounting state. That way isMounted
// works as expected. We must reset this before committing.
// TODO: Delete this when we delete isMounted and findDOMNode.
workInProgress.effectTag |= Placement;
// Ensure that children mount into this root without tracking
// side-effects. This ensures that we don't store Placement effects on
// nodes that will be hydrated.
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderExpirationTime);
} else {
// Otherwise reset hydration state in case we aborted and resumed another
// root.
reconcileChildren(current$$1, workInProgress, nextChildren, renderExpirationTime);
resetHydrationState();
}
return workInProgress.child;
}
performUnitOfWork会将workInProgress.alternate赋值给current
1, workInProgress传入updateHostRoot中。
这个时候current$$1的child是为null的,所以在updateHostRoot之前,其实是并不知道这个Fiber中组件的子级是什么内容。
我们看到child是为null的。接着processUpdateQueue会将updateQueue中的firstUpdate中的payload.element赋值到workInProgress的memoizedState中。说白了就是把任务队列中要渲染的ReactElement提取出来并且赋值到nextChildren变量中,之后执行reconcileChildren。
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.child = reconcileChildFibers(workInProgress, current$$1.child, nextChildren, renderExpirationTime);
}
}
如代码所见,会将reconcileChildFibers执行后的结果返回到workInProgress.child上。
// This API will tag the children with the side-effect of the reconciliation
// itself. They will be added to the side-effect list as we pass through the
// children and the parent.
function reconcileChildFibers(returnFiber, currentFirstChild, newChild, expirationTime) {
// This function is not recursive.
// If the top level item is an array, we treat it as a set of children,
// not as a fragment. Nested arrays on the other hand will be treated as
// fragment nodes. Recursion happens at the normal flow.
// Handle top level unkeyed fragments as if they were arrays.
// This leads to an ambiguity between <>{[...]}</> and <>...</>.
// We treat the ambiguous cases above the same.
var isUnkeyedTopLevelFragment = typeof newChild === 'object' && newChild !== null && newChild.type === REACT_FRAGMENT_TYPE && newChild.key === null;
if (isUnkeyedTopLevelFragment) {
newChild = newChild.props.children;
}
// Handle object types
var isObject = typeof newChild === 'object' && newChild !== null;
if (isObject) {
switch (newChild.$$typeof) {
case REACT_ELEMENT_TYPE:
return placeSingleChild(reconcileSingleElement(returnFiber, currentFirstChild, newChild, expirationTime));
case REACT_PORTAL_TYPE:
return placeSingleChild(reconcileSinglePortal(returnFiber, currentFirstChild, newChild, expirationTime));
}
}
if (typeof newChild === 'string' || typeof newChild === 'number') {
return placeSingleChild(reconcileSingleTextNode(returnFiber, currentFirstChild, '' + newChild, expirationTime));
}
if (isArray(newChild)) {
return reconcileChildrenArray(returnFiber, currentFirstChild, newChild, expirationTime);
}
if (getIteratorFn(newChild)) {
return reconcileChildrenIterator(returnFiber, currentFirstChild, newChild, expirationTime);
}
if (isObject) {
throwOnInvalidObjectType(returnFiber, newChild);
}
{
if (typeof newChild === 'function') {
warnOnFunctionType();
}
}
if (typeof newChild === 'undefined' && !isUnkeyedTopLevelFragment) {
// If the new child is undefined, and the return fiber is a composite
// component, throw an error. If Fiber return types are disabled,
// we already threw above.
switch (returnFiber.tag) {
case ClassComponent:
{
{
var instance = returnFiber.stateNode;
if (instance.render._isMockFunction) {
// We allow auto-mocks to proceed as if they're returning null.
break;
}
}
}
// Intentionally fall through to the next case, which handles both
// functions and classes
// eslint-disable-next-lined no-fallthrough
case FunctionComponent:
{
var Component = returnFiber.type;
invariant(false, '%s(...): Nothing was returned from render. This usually means a return statement is missing. Or, to render nothing, return null.', Component.displayName || Component.name || 'Component');
}
}
}
// Remaining cases are all treated as empty.
return deleteRemainingChildren(returnFiber, currentFirstChild);
}
return reconcileChildFibers;
}
当前传入的newChild:
举其中一个例子:
会先后经过reconcileSingleElement和placeSingleChild两个函数对child进行实例化成一个Fiber对象,最终赋值给workInProgress.child。最终回到performUnitOfWork函数中,经过调用beginWork函数后,赋值给了next变量,现在这个变量的child中已经实例了一个App的Fiber对象出来了。
下一篇继续阅读如何通过workLoop进行Fiber树的生成。