前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >小前端读源码 - React16.7.0(三)

小前端读源码 - React16.7.0(三)

作者头像
LamHo
发布2022-09-26 10:37:34
2840
发布2022-09-26 10:37:34
举报
文章被收录于专栏:小前端看世界

继续上边说道的将fiber传入了scheduleWork函数中,接着如何一步一步去进行渲染呢?

Lam:小前端读源码 - React16.7.0(二)

在scheduleWork内部会将fiber传入一个scheduleWorkToRoot进行加工。

scheduleWorkToRoot

在scheduleWorkToRoot中会对fiber的expirationTime进行赋值,然后返回fiber中的stateNode出来。之后会传入一个markPendingPriorityLevel的函数,但是我们暂时不关注它,因为它应该是做异步渲染的。之后就会将root和expirationTime传入到一个requestWork的函数中。

requestWork

requestWork中会调用addRootToSchedule,最后会将root赋值到全局的firstScheduledRoot和lastScheduledRoot中。其实也是有很多异步渲染的工作会做,但是我们都先不关注它,直接看到performSyncWork这个函数中,因为我们都是以同步的方式进行工作,所以也只会进入这个函数中。

performSyncWork内部performWork。

performWork

performWork中立马会执行findHighestPriorityRoot这个函数,这个函数的目的就是将firstScheduledRoot赋值到nextFlushedRoot。

紧接着就会将nextFlushedRoot传入performWorkOnRoot当中。

performWorkOnRoot

首先会将全局的isRendering设置为true。之后就会开始执行 renderRoot函数了。其实在执行renderRoot之前,我们回顾一下我们的DEMO。

代码语言:javascript
复制
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实例化成一个对象出来。

renderRoot

更换名称:nextRoot = root;

在renderRoot中,会先将nextRoot.current传入createWorkInProgress中,createWorkInProgress会将传入的current的内容生成一个Fiber对象,以下是createWorkInProgress的代码:

代码语言:javascript
复制
// 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了!!!

workLoop

代码语言:javascript
复制
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为止。

performUnitOfWork

修改命名:workInProgress == nextUnitOfWork

代码语言:javascript
复制
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,然后传入beginWork函数中。beginWork最终会将current

1, workInProgress传入updateHostRoot中。

这个时候current$$1的child是为null的,所以在updateHostRoot之前,其实是并不知道这个Fiber中组件的子级是什么内容。

  • memoizedProps 更新后的props
  • pendingProps 更新前的props

我们看到child是为null的。接着processUpdateQueue会将updateQueue中的firstUpdate中的payload.element赋值到workInProgress的memoizedState中。说白了就是把任务队列中要渲染的ReactElement提取出来并且赋值到nextChildren变量中,之后执行reconcileChildren。

reconcileChildren

代码语言:javascript
复制
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上。

reconcileChildFibers

代码语言:javascript
复制
// 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树的生成。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-01-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • scheduleWorkToRoot
  • requestWork
  • performWork
  • performWorkOnRoot
  • renderRoot
  • workLoop
  • performUnitOfWork
  • reconcileChildren
  • reconcileChildFibers
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档