react源码阅读二

react源码阅读二

Renderer 篇

  1. Fiber节点如何被创建,并且构建Fiber树?
    render 阶段 开始于 performSyncWorkOnRoot 和performConcurrentWorkOnRoot 这两个方法的调用,至此,先不学这两个方法,
// performSyncWorkOnRoot会调用该方法
function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

// performConcurrentWorkOnRoot会调用该方法
function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}
可以看到,两者区别只是,是否调用shouldYield 方法,若是当前帧没得剩余时间,shouldYield ,会终止循环,等到浏览器有剩余时间,继续遍历。

注:
workInprogress 代表当前创建的Fiber
performUnitOfWork方法会创建下一个Fiber节点并赋值给 workInProgress,并将workInProgress与已创建的Fiber节点连接起来构成Fiber树。

Fiber Reconciler 的 ‘’ 递’’ 和’‘归’’ 阶段

  1. Fiber Reconciler 首先是从 Stack Reconciler 阶段重构来的,通过遍历的方式,实现可中断的递归,
  2. “ 递”的阶段
    首先从rootFiber开始向下深度优先遍历。为遍历到的每个Fiber节点调用beginWork方法 。该方法会根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来。当遍历到叶子节点(即没有子组件的组件)时就会进入“归”阶段。
// 源码的位置 根据传入的fiber节点来创建子Fiber节点, 
function beginWork(
//  当前组件对应的Fiber节点在上一次更新时的Fiber节点,即workInProgress.alternate
  current: Fiber | null,
// 当前组件对应的Fiber节点
  workInProgress: Fiber,
//   优先级相关,在学习Scheduler时再讲解
  renderLanes: Lanes,
): Fiber | null {
  let updateLanes = workInProgress.lanes;

  if (__DEV__) {
    if (workInProgress._debugNeedsRemount && current !== null) {
      // This will restart the begin phase with a new fiber.
      return remountFiber(
        current,
        workInProgress,
        createFiberFromTypeAndProps(
          workInProgress.type,
          workInProgress.key,
          workInProgress.pendingProps,
          workInProgress._debugOwner || null,
          workInProgress.mode,
          workInProgress.lanes,
        ),
      );
    }
  }
// **update 阶段** 
//注释解:**oldProps === newProps && workInProgress.type === current.type,即props与fiber.type不变
//!includesSomeLane(renderLanes, updateLanes),即当前Fiber节点优先级不够,会在讲解Scheduler时介绍**
  if (current !== null) {
    // TODO: The factoring of this block is weird.
    if (
      enableLazyContextPropagation &&
      !includesSomeLane(renderLanes, updateLanes)
    ) {
      const dependencies = current.dependencies;
      if (dependencies !== null && checkIfContextChanged(dependencies)) {
        updateLanes = mergeLanes(updateLanes, renderLanes);
      }
    }

    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    if (
      oldProps !== newProps ||
      hasLegacyContextChanged() ||
      // Force a re-render if the implementation changed due to hot reload:
      (__DEV__ ? workInProgress.type !== current.type : false)
    ) {
      // If props or context changed, mark the fiber as having performed work.
      // This may be unset if the props are determined to be equal later (memo).
      didReceiveUpdate = true;
    } else if (!includesSomeLane(renderLanes, updateLanes)) {
      didReceiveUpdate = false;
      // This fiber does not have any pending work. Bailout without entering
      // the begin phase. There's still some bookkeeping we that needs to be done
      // in this optimized path, mostly pushing stuff onto the stack.
      switch (workInProgress.tag) {
        case HostRoot:
          pushHostRootContext(workInProgress);
          if (enableCache) {
            const root: FiberRoot = workInProgress.stateNode;
            const cache: Cache = current.memoizedState.cache;
            pushCacheProvider(workInProgress, cache);
            pushRootCachePool(root);
          }
          resetHydrationState();
          break;
        case HostComponent:
          pushHostContext(workInProgress);
          break;
        case ClassComponent: {
          const Component = workInProgress.type;
          if (isLegacyContextProvider(Component)) {
            pushLegacyContextProvider(workInProgress);
          }
          break;
        }
        case HostPortal:
          pushHostContainer(
            workInProgress,
            workInProgress.stateNode.containerInfo,
          );
          break;
        case ContextProvider: {
          const newValue = workInProgress.memoizedProps.value;
          const context: ReactContext<any> = workInProgress.type._context;
          pushProvider(workInProgress, context, newValue);
          break;
        }
        case Profiler:
          if (enableProfilerTimer) {
            // Profiler should only call onRender when one of its descendants actually rendered.
            const hasChildWork = includesSomeLane(
              renderLanes,
              workInProgress.childLanes,
            );
            if (hasChildWork) {
              workInProgress.flags |= Update;
            }

            if (enableProfilerCommitHooks) {
              // Reset effect durations for the next eventual effect phase.
              // These are reset during render to allow the DevTools commit hook a chance to read them,
              const stateNode = workInProgress.stateNode;
              stateNode.effectDuration = 0;
              stateNode.passiveEffectDuration = 0;
            }
          }
          break;
        case SuspenseComponent: {
          const state: SuspenseState | null = workInProgress.memoizedState;
          if (state !== null) {
            if (enableSuspenseServerRenderer) {
              if (state.dehydrated !== null) {
                pushSuspenseContext(
                  workInProgress,
                  setDefaultShallowSuspenseContext(suspenseStackCursor.current),
                );
                // We know that this component will suspend again because if it has
                // been unsuspended it has committed as a resolved Suspense component.
                // If it needs to be retried, it should have work scheduled on it.
                workInProgress.flags |= DidCapture;
                // We should never render the children of a dehydrated boundary until we
                // upgrade it. We return null instead of bailoutOnAlreadyFinishedWork.
                return null;
              }
            }

            // If this boundary is currently timed out, we need to decide
            // whether to retry the primary children, or to skip over it and
            // go straight to the fallback. Check the priority of the primary
            // child fragment.
            const primaryChildFragment: Fiber = (workInProgress.child: any);
            const primaryChildLanes = primaryChildFragment.childLanes;
            if (includesSomeLane(renderLanes, primaryChildLanes)) {
              // The primary children have pending work. Use the normal path
              // to attempt to render the primary children again.
              return updateSuspenseComponent(
                current,
                workInProgress,
                renderLanes,
              );
            } else {
              // The primary child fragment does not have pending work marked
              // on it
              pushSuspenseContext(
                workInProgress,
                setDefaultShallowSuspenseContext(suspenseStackCursor.current),
              );
              // The primary children do not have pending work with sufficient
              // priority. Bailout.
              const child = bailoutOnAlreadyFinishedWork(
                current,
                workInProgress,
                renderLanes,
              );
              if (child !== null) {
                // The fallback children have pending work. Skip over the
                // primary children and work on the fallback.
                return child.sibling;
              } else {
                // Note: We can return `null` here because we already checked
                // whether there were nested context consumers, via the call to
                // `bailoutOnAlreadyFinishedWork` above.
                return null;
              }
            }
          } else {
            pushSuspenseContext(
              workInProgress,
              setDefaultShallowSuspenseContext(suspenseStackCursor.current),
            );
          }
          break;
        }
        case SuspenseListComponent: {
          const didSuspendBefore = (current.flags & DidCapture) !== NoFlags;

          let hasChildWork = includesSomeLane(
            renderLanes,
            workInProgress.childLanes,
          );

          if (enableLazyContextPropagation && !hasChildWork) {
            // Context changes may not have been propagated yet. We need to do
            // that now, before we can decide whether to bail out.
            // TODO: We use `childLanes` as a heuristic for whether there is
            // remaining work in a few places, including
            // `bailoutOnAlreadyFinishedWork` and
            // `updateDehydratedSuspenseComponent`. We should maybe extract this
            // into a dedicated function.
            lazilyPropagateParentContextChanges(
              current,
              workInProgress,
              renderLanes,
            );
            hasChildWork = includesSomeLane(
              renderLanes,
              workInProgress.childLanes,
            );
          }

          if (didSuspendBefore) {
            if (hasChildWork) {
              // If something was in fallback state last time, and we have all the
              // same children then we're still in progressive loading state.
              // Something might get unblocked by state updates or retries in the
              // tree which will affect the tail. So we need to use the normal
              // path to compute the correct tail.
              return updateSuspenseListComponent(
                current,
                workInProgress,
                renderLanes,
              );
            }
            // If none of the children had any work, that means that none of
            // them got retried so they'll still be blocked in the same way
            // as before. We can fast bail out.
            workInProgress.flags |= DidCapture;
          }

          // If nothing suspended before and we're rendering the same children,
          // then the tail doesn't matter. Anything new that suspends will work
          // in the "together" mode, so we can continue from the state we had.
          const renderState = workInProgress.memoizedState;
          if (renderState !== null) {
            // Reset to the "together" mode in case we've started a different
            // update in the past but didn't complete it.
            renderState.rendering = null;
            renderState.tail = null;
            renderState.lastEffect = null;
          }
          pushSuspenseContext(workInProgress, suspenseStackCursor.current);

          if (hasChildWork) {
            break;
          } else {
            // If none of the children had any work, that means that none of
            // them got retried so they'll still be blocked in the same way
            // as before. We can fast bail out.
            return null;
          }
        }
        case OffscreenComponent:
        case LegacyHiddenComponent: {
          // Need to check if the tree still needs to be deferred. This is
          // almost identical to the logic used in the normal update path,
          // so we'll just enter that. The only difference is we'll bail out
          // at the next level instead of this one, because the child props
          // have not changed. Which is fine.
          // TODO: Probably should refactor `beginWork` to split the bailout
          // path from the normal path. I'm tempted to do a labeled break here
          // but I won't :)
          workInProgress.lanes = NoLanes;
          return updateOffscreenComponent(current, workInProgress, renderLanes);
        }
        case CacheComponent: {
          if (enableCache) {
            const cache: Cache = current.memoizedState.cache;
            pushCacheProvider(workInProgress, cache);
          }
          break;
        }
      }
      return bailoutOnAlreadyFinishedWork(current, workInProgress, renderLanes);
    } else {
      if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
        // This is a special case that only exists for legacy mode.
        // See https://github.com/facebook/react/pull/19216.
        didReceiveUpdate = true;
      } else {
        // An update was scheduled on this fiber, but there are no new props
        // nor legacy context. Set this to false. If an update queue or context
        // consumer produces a changed value, it will set this to true. Otherwise,
        // the component will assume the children have not changed and bail out.
        didReceiveUpdate = false;
      }
    }
  } else {
    didReceiveUpdate = false;
  }

  // Before entering the begin phase, clear pending update priority.
  // TODO: This assumes that we're about to evaluate the component and process
  // the update queue. However, there's an exception: SimpleMemoComponent
  // sometimes bails out later in the begin phase. This indicates that we should
  // move this assignment out of the common path and into each branch.
  workInProgress.lanes = NoLanes;
 // mount 阶段
  // 以下case 省略 
  switch (workInProgress.tag) {
    case IndeterminateComponent: {}
    case LazyComponent: {}
    case FunctionComponent: {}
    case ClassComponent: {}
    case HostRoot:;
    case HostComponent:
    case HostText:;
    case SuspenseComponent:;
    case HostPortal:;
    case ForwardRef: {}
    case Fragment:;
    case Mode:;
    case Profiler:;
    case ContextProvider:;
    case ContextConsumer:{}
    case SimpleMemoComponent: {}
    case IncompleteClassComponent: {}
    case SuspenseListComponent: {}
    case ScopeComponent: {}
    case OffscreenComponent: {}
    case LegacyHiddenComponent: {}
    case CacheComponent: {
      if (enableCache) {
        return updateCacheComponent(
          current,
          workInProgress,
          updateLanes,
          renderLanes,
        );
      }
      break;
    }
  }
  invariant(
    false,
    'Unknown unit of work tag (%s). This error is likely caused by a bug in ' +
      'React. Please file an issue.',
    workInProgress.tag,
  );
}
  1. 对于我们常见的类组件、函数组件、原生组件,最后会进入reconcileChildren这个方法。如下:
export function reconcileChildren(
  current: Fiber | null,
  workInProgress: Fiber,
  nextChildren: any,
  renderLanes: Lanes,
) {
  if (current === 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.
    // mount 的组件
    workInProgress.child = mountChildFibers(
      workInProgress,
      null,
      nextChildren,
      renderLanes,
    );
  } 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.
    // update 组件
    workInProgress.child = reconcileChildFibers(
      workInProgress,
      current.child,
      nextChildren,
      renderLanes,
    );
  }
}
  1. effectTag
    通常render阶段是在内存中进行的,当结束之后会通知render 需要执行的Dom操作。具体操作的Dom类型在
    packages/react-reconciler/src/ReactWorkTags.js中。
    1. 若要通知,render将fiber节点插入到Dom中,则需要两个条件:
      1. fiber.stateNode存在,即Fiber节点中保存了对应的DOM节点
      2. (fiber.effectTag & Placement) !== 0,即Fiber节点存在Placement effectTag
        我们知道,mount时,fiber.stateNode === null,且在reconcileChildren中调用的mountChildFibers不会为Fiber节点赋值effectTag。那么首屏渲染如何完成呢?针对第一个问题,fiber.stateNode会在completeWork中创建,我们会在下一节介绍。第二个问题的答案十分巧妙:假设mountChildFibers也会赋值effectTag,那么可以预见mount时整棵Fiber树所有节点都会有Placement effectTag。那么commit阶段在执行DOM操作时每个节点都会执行一次插入操作,这样大量的DOM操作是极低效的。为了解决这个问题,在mount时只有rootFiber会赋值Placement effectTag,在commit阶段只会执行一次插入操作。

模拟react-Fiber链表的遍历实现

  1. 声明一个节点类
class Node {
    constructor(instance) {
      this.instance = instance;
      this.child = null;
      this.Sibling = null;
      this.return = null;
    }
}
  1. 获取 一个节点数组,并且将它们链接在一起的函数 使用它来 链接 render 方法

function link(parent, elements) {
    if (elements === null) elements = [];
		parent.child = elements.reduceRight((previous, current) => {
        const node = new Node(current);
        node.return = parent;
        node.sibling = previous;
        return node;
    }, null);
  return parent.child;
  1. 该函数迭代从最后一个节点开始的节点数组,并且将它们链接到单独的链表中。返回列表中第一个同级的引用,下面演示如何工作:

     const children = [{name: 'b1'}, {name: 'b2'}];
     const parent = new Node({name: 'a1'});
     const child = link(parent, children);
    
     console.log(child.instance.name === 'b1');
     console.log(child.sibling.instance === children[1]);
    
  2. 我们还将实现一个helper函数,为节点执行一些工作。在我们的例子中,它将记录组件的名称。但除此之外,它还检索组件的子级并将它们链接在一起:

function doWork(node) {
    console.log(node.instance.name);
    const children = node.instance.render();
    return link(node, children);
}
  1. 父级优先,fiber树的深度遍历
function walk(o) {
    let root = o;
    let current = o;
    while (true) {
        // 此循环我们可以随时停止遍历,稍后再继续。这种效果刚好满足我们的条件,可以使用新的requestIdleCallback 编程接口
        // perform work for a node, retrieve & link the children
        let child = doWork(current);
        // if there's a child, set it as the current active node
        if (child) {
            current = child;
            continue;
        }
        // if we've returned to the top, exit the function
        if (current === root) {
            return;
        }
        // keep going up until we find the sibling
        while (!current.sibling) {
            // if we've returned to the top, exit the function
            if (!current.return || current.return === root) {
                return;
            }
            // set the parent as the current active node
            current = current.return;
        }
        // if found, set the sibling as the current active node
        current = current.sibling;
    }
}
  1. performUnitOfWork实现工作原理
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值