React核心源码解析

React 核心源码侧重点在于16.8架构,而 React18 架构更加侧重的是服务端 SSR 相关的处理,也就是说16.8之后整体架构思想是类似的。

本文将对比 React15,以及16.8之后的版本来对 React 核心源码进行解析,包含fiber、双缓存、JSX编译、diff算法等内容。

React 框架特性

  • 快速响应

  • 单向数据流 ui = render(data)

如何做到快速响应?

这个问题可以转化成什么会导致响应慢,主要有两点:

  • CPU卡顿

  • IO卡顿

怎么解决这两个问题?

CPU卡顿

浏览器刷新频率一般是60Hz,所以我们每一帧就是 1000ms / 60Hz = 16.6ms

如果我们每一帧的执行时间大于16.6ms,那么用户就会感受到卡顿,也就是掉帧。

我们的 JS 线程和 GUI 线程是互斥的,所以 JS 执行和样式的布局和绘制不能同时进行,一旦 JS 执行时间超过16.6ms,就不会执行布局和绘制。

对此,React 采用了分片的方法,将耗时长的任务分为许多小片,在每一帧当中预留 5ms 的时间给 JS 线程,再执行其他任务(渲染等),也就实现了非阻塞渲染。

let yieldInterval = 5;

当然,也可以根据刷新率来改变yieldInterval

forceFrameRate = function(fps) {
  if (fps < 0 || fps > 125) {
    // Using console['error'] to evade Babel and ESLint
    console['error'](
      'forceFrameRate takes a positive int between 0 and 125, ' +
      'forcing frame rates higher than 125 fps is not unsupported',
    );
    return;
  }
  if (fps > 0) {
    yieldInterval = Math.floor(1000 / fps);
  } else {
    // reset the framerate
    yieldInterval = 5;
  }
};

我们使用React框架时,是通过 createRoot 来开启分片的能力,也就是 concurrent mode,这是16.8提供的能力。

ReactDOM.createRoot(rootEl).render(<App/>);

IO卡顿

IO卡顿是由于网络请求延迟造成的卡顿,React 中可以通过显示 loading,加载完成后隐藏来解决,也就是Suspense,用户会看见骨架屏,而不是留一个白屏给用户。这样做增加了用户体验。

<Suspense fallback={<Loading />}>
  <SomeComponent />
</Suspense>

我们也可以根据任务的优先级来处理,优先响应优先级高的任务。

总之,我们快速响应的核心就是将同步的长任务转变为可中断的异步任务。

React15

React15的架构只有同步任务,它有两个核心概念:

  • Reconciler 协调器

  • Renderer 渲染器

Reconciler

协调器Reconciler负责找出变化的组件,this.setState、this.forceUpdate、ReactDOM.render 这些API都会触发我们的页面更新,就会调用 Reconciler。

Reconciler的工作如下:

  1. 调用函数组件或是类组件的 render 方法,将JSX转化为虚拟DOM

  2. 将虚拟DOM和上次更新时的虚拟DOM对比

  3. 通过对比找出需要改变的虚拟DOM

  4. 通知Renderer将变化后的虚拟DOM渲染到页面上

Renderer

React 支持跨平台,不同的平台有不同的Renderer,除了我们最熟悉的 ReactDOM 以外还有 ReactNative、ReactArt等,负责将虚拟DOM渲染到页面上。

缺点

mount组件时,会调用mountComponent方法,它会根据标签调用mountWrapper,而mountWrapper又会调用mountComponent方法,也就是进行了递归的过程。

mountComponent: function(...) {
  ...
  switch (this._tag) {
    case 'audio':
    case 'form':
    case 'iframe':
    case 'img':
    case 'link':
    case 'object':
    case 'source':
    case 'video':
      this._wrapperState = {
        listeners: null,
      };
      transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
      break;
    case 'input':
      ReactDOMInput.mountWrapper(this, props, hostParent);
      props = ReactDOMInput.getHostProps(this, props);
      transaction.getReactMountReady().enqueue(trackInputValue, this);
      transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
      break;
    case 'option':
      ReactDOMOption.mountWrapper(this, props, hostParent);
      props = ReactDOMOption.getHostProps(this, props);
      break;
    case 'select':
      ReactDOMSelect.mountWrapper(this, props, hostParent);
      props = ReactDOMSelect.getHostProps(this, props);
      transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
      break;
    case 'textarea':
      ReactDOMTextarea.mountWrapper(this, props, hostParent);
      props = ReactDOMTextarea.getHostProps(this, props);
      transaction.getReactMountReady().enqueue(trackInputValue, this);
      transaction.getReactMountReady().enqueue(trapBubbledEventsLocal, this);
      break;
  }

  assertValidProps(this, props);
  ...

  switch (this._tag) {
    case 'input':
      transaction.getReactMountReady().enqueue(inputPostMount, this);
      if (props.autoFocus) {
        transaction
          .getReactMountReady()
          .enqueue(AutoFocusUtils.focusDOMComponent, this);
      }
      break;
    case 'textarea':
      transaction.getReactMountReady().enqueue(textareaPostMount, this);
      if (props.autoFocus) {
        transaction
          .getReactMountReady()
          .enqueue(AutoFocusUtils.focusDOMComponent, this);
      }
      break;
    case 'select':
      if (props.autoFocus) {
        transaction
          .getReactMountReady()
          .enqueue(AutoFocusUtils.focusDOMComponent, this);
      }
      break;
    case 'button':
      if (props.autoFocus) {
        transaction
          .getReactMountReady()
          .enqueue(AutoFocusUtils.focusDOMComponent, this);
      }
      break;
    case 'option':
      transaction.getReactMountReady().enqueue(optionPostMount, this);
      break;
  }

  return mountImage;
}

同理,update 组件时会触发updateComponent方法,也会进行递归。

如果递归层级很深,那么执行时间超过16ms时,用户交互就会卡顿。

并且React15采用的递归方式不支持中断。

React16

相比React15,React16增加了Scheduler调度器,负责让高优先级的任务先进入Reconciler执行。

对于优先级调度,很多人最先会想到requestIdleCallback这个API,然而因为它不兼容safari,并且触发频率不稳定,因此React实现了自己的requestIdleCallbackpolyfill

React16还实现了workLoopConcurrent,用可中断的异步任务来代替递归遍历的方式,通过shouldYield来判断是否需要中断。

function workLoopConcurrent() {
  while (workInProgress !== null && !shouldYield()) {
    performUnitOfWork(workInProgress);
  }
}

这里的workInProgress在Fiber中赘述

在React16中,Reconciler与Renderer不再是交替工作。当Scheduler将任务交给Reconciler后,Reconciler会为变化的虚拟DOM打上代表增/删/更新的标记。

那么,异步可中断是如何实现的呢?

这就是React16架构的核心——Fiber。

Fiber

Fiber是一种数据结构,每个Fiber对应一个element,保存它的组件类型、优先级、调度的上下文等信息。

  • 静态节点的信息

this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null;
  • 链接其他Fiber节点的指针

this.return = null; // 父节点
this.child = null; // 子节点
this.sibling = null; // 右边第一个兄弟节点
this.index = 0;

this.ref = null;
  • 动态属性,调度的上下文

this.pendingProps = pendingProps;
this.memoizedProps = null;
this.updateQueue = null;
this.memoizedState = null;
this.dependencies = null;

this.mode = mode;

this.effectTag = NoEffect;
this.subtreeTag = NoSubtreeEffect;
this.deletions = null;
this.nextEffect = null;

this.firstEffect = null;
this.lastEffect = null;
  • 调度的优先级

this.lanes = NoLanes;
this.childLanes = NoLanes;
  • 指向下一次更新时对应的Fiber

this.alternate = null;

双缓存

如何更新DOM,我们React16给出的方法是双缓存,我们在内存中绘制当前的Fiber,绘制完毕后替换上一帧的Fiber,我们把当前渲染的Fiber成为current Fiber,而内存中的是workInProgress Fiber,也就是WIP Fiber。

这两个Fiber的alternate指针互相指向对方。

JSX

JSX 和 Fiber节点不是一个概念,JSX不包含调度的优先级,上下文等内容,并且JSX是createElement的语法糖,要经过babel编译为createElement方法。

export function createElement(type, config, children) {
  let propName;

  const props = {};

  let key = null;
  let ref = null;
  let self = null;
  let source = null;

  if (config != null) {
    // 将 config 处理后赋值给 props
    ...
  }

  const childrenLength = arguments.length - 2;
  ...

  return ReactElement(
    type,
    key,
    ref,
    self,
    source,
    ReactCurrentOwner.current,
    props,
  );
}

const ReactElement = function(type, key, ref, self, source, owner, props) {
  const element = {
    // 标记这是个 React Element
    $$typeof: REACT_ELEMENT_TYPE,

    type: type,
    key: key,
    ref: ref,
    props: props,
    _owner: owner,
  };

  return element;
};

React执行流程

状态更新

在React中状态更新会创建一个Update对象,保存更新状态的相关信息,在render阶段的beginWork中会获取到新的state。

const update: Update<*> = {
  eventTime,
  lane,
  suspenseConfig,
  tag: UpdateState,
  payload: null,
  callback: null,

  next: null, // 指向下一个Update
};

Fiber节点中存在updateQueue,他负责维护更新的上下文

const queue: UpdateQueue<State> = {
  baseState: fiber.memoizedState,
  firstBaseUpdate: null,
  lastBaseUpdate: null,
  shared: {
    pending: null, // 环形链表的形式存储更新时的Update对象
  },
  effects: null,
};

state的变化在render阶段产生与上次更新不同的JSX对象,通过Diff算法产生effectTag,在commit阶段渲染在页面上,同时,渲染完成后workInProgress Fiber树变为current Fiber树,整个更新流程结束。

render阶段

在render阶段,Scheduler和Reconciler进行工作,生成Fiber并且构建成render树。

无论是同步遍历还是异步遍历,采用的都是递归的思路。

“递”会调用beginWork,它是一个深度优先遍历的过程,根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来,如果遍历到叶子结点,就会进入“归”的过程。

“归”会调用completeWork,当执行完completeWork,会判断当前Fiber节点右边是否存在兄弟节点,如果存在,就执行兄弟节点的“递”的过程,如果不存在,就执行父节点的“归”的过程。

beginWork

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {

  if (current !== null) {
    const oldProps = current.memoizedProps;
    const newProps = workInProgress.pendingProps;

    if (
      oldProps !== newProps ||
      hasLegacyContextChanged() ||
      (__DEV__ ? workInProgress.type !== current.type : false)
    ) {
      didReceiveUpdate = true;
    } else {
      const hasScheduledUpdateOrContext = checkScheduledUpdateOrContext(
        current,
        renderLanes,
      );
      if (
        !hasScheduledUpdateOrContext &&
        (workInProgress.flags & DidCapture) === NoFlags
      ) {
        didReceiveUpdate = false;
        return attemptEarlyBailoutIfNoScheduledUpdate(
          current,
          workInProgress,
          renderLanes,
        );
      }
      if ((current.flags & ForceUpdateForLegacySuspense) !== NoFlags) {
        didReceiveUpdate = true;
      } else {
        didReceiveUpdate = false;
      }
    }
  } else {
    didReceiveUpdate = false;

    if (getIsHydrating() && isForkedChild(workInProgress)) {
      const slotIndex = workInProgress.index;
      const numberOfForks = getForksAtLevel(workInProgress);
      pushTreeId(workInProgress, numberOfForks, slotIndex);
    }
  }

  workInProgress.lanes = NoLanes;

  switch (workInProgress.tag) {
    case ...
  }

}

可以看到beginWork会根据current是否为空来判断组件是应该mount还是update

  • mount时,会根据传入的WIP的tag的不同来创建不同类型子的Fiber节点。

  • update时会判断是否可以复用current节点,这样就能克隆current.childworkInProgress.child

diff算法

update时,需要使用一套算法来判断current Fiber和WIP Fiber中哪些节点是可以复用的,哪些是需要改变的。

diff算法需要处理以下一些情况:

  • 不同类型的元素:销毁旧节点,创建新节点

  • 同一类型,属性不同的元素:更新组件的props

那么,我们的diff算法要比较的是什么呢?

和DOM相关的节点有以下几种:

  • current Fiber

  • JSX对象

  • DOM本身

  • workInProgress Fiber

我们通过比较current Fiber和JSX对象,生成workInProgress Fiber,最终用workInProgress Fiber替换current Fiber。

diff算法的入口是reconcileChildFibers函数,他会根据JSX对象的类型做不同的处理。

function reconcileChildFibersImpl(
  returnFiber: Fiber, // 当前节点的父节点
  currentFirstChild: Fiber | null, // 当前节点
  newChild: any, // 新元素
  lanes: Lanes,
): Fiber | null {

  const isUnkeyedTopLevelFragment =
    typeof newChild === 'object' &&
    newChild !== null &&
    newChild.type === REACT_FRAGMENT_TYPE &&
    newChild.key === null;
  if (isUnkeyedTopLevelFragment) {
    newChild = newChild.props.children;
  }

  if (typeof newChild === 'object' && newChild !== null) {
    switch (newChild.$$typeof) {
      case REACT_ELEMENT_TYPE:
        return placeSingleChild(
          reconcileSingleElement(
            returnFiber,
            currentFirstChild,
            newChild,
            lanes,
          ),
        );
      ...
    }
    // 针对数组,要进行多节点的处理
    if (isArray(newChild)) {
      return reconcileChildrenArray(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes,
      );
    }

    if (getIteratorFn(newChild)) {
      return reconcileChildrenIterator(
        returnFiber,
        currentFirstChild,
        newChild,
        lanes,
      );
    }

    if (typeof newChild.then === 'function') {
      const thenable: Thenable<any> = (newChild: any);
      return reconcileChildFibersImpl(
        returnFiber,
        currentFirstChild,
        unwrapThenable(thenable),
        lanes,
      );
    }

    if (
      newChild.$$typeof === REACT_CONTEXT_TYPE ||
      newChild.$$typeof === REACT_SERVER_CONTEXT_TYPE
    ) {
      const context: ReactContext<mixed> = (newChild: any);
      return reconcileChildFibersImpl(
        returnFiber,
        currentFirstChild,
        readContextDuringReconcilation(returnFiber, context, lanes),
        lanes,
      );
    }

    throwOnInvalidObjectType(returnFiber, newChild);
  }
  // 文本节点
  if (
    (typeof newChild === 'string' && newChild !== '') ||
    typeof newChild === 'number'
  ) {
    return placeSingleChild(
      reconcileSingleTextNode(
        returnFiber,
        currentFirstChild,
        '' + newChild,
        lanes,
      ),
    );
  }

  return deleteRemainingChildren(returnFiber, currentFirstChild);
}

针对不同的节点,它会调用不同的函数处理:

单节点
function reconcileSingleElement(
  returnFiber: Fiber,
  currentFirstChild: Fiber | null,
  element: ReactElement,
  lanes: Lanes,
): Fiber {
  const key = element.key;
  let child = currentFirstChild;
  // 通过child !== null来判断在current Fiber中是否存在节点,如果不存在就新增
  while (child !== null) {
    // 首先判断key是否相同
    if (child.key === key) {
      const elementType = element.type;
      if (elementType === REACT_FRAGMENT_TYPE) {
        if (child.tag === Fragment) {
          // 如果是单节点,就要删除其余的兄弟节点
          deleteRemainingChildren(returnFiber, child.sibling);
          const existing = useFiber(child, element.props.children);
          existing.return = returnFiber;
          if (__DEV__) {
            existing._debugSource = element._source;
            existing._debugOwner = element._owner;
          }
          return existing;
        }
      } else {
        // 如果是普通的element节点
        if (child.elementType === elementType) {
          deleteRemainingChildren(returnFiber, child.sibling);
          const existing = useFiber(child, element.props);
          existing.ref = coerceRef(returnFiber, child, element);
          existing.return = returnFiber;
          if (__DEV__) {
            existing._debugSource = element._source;
            existing._debugOwner = element._owner;
          }
          return existing;
        }
      }
      // 没有匹配到就删除
      deleteRemainingChildren(returnFiber, child);
      break;
    } else {
      deleteChild(returnFiber, child);
    }
    child = child.sibling;
  }

  if (element.type === REACT_FRAGMENT_TYPE) {
    const created = createFiberFromFragment(
      element.props.children,
      returnFiber.mode,
      lanes,
      element.key,
    );
    created.return = returnFiber;
    return created;
  } else {
    const created = createFiberFromElement(element, returnFiber.mode, lanes);
    created.ref = coerceRef(returnFiber, currentFirstChild, element);
    created.return = returnFiber;
    return created;
  }
}

先判断key是否相同,如果相同判断type是否相同,如果都相同就复用节点;

如果key相同,type不同,添加删除标记;

key不同,也添加删除标记。

多节点

是否可以使用类似Vue2的双端diff算法?

不可以,因为React的Fiber使用的是单链表的数据结构,不能用指针。而JSX对象,newChildren是个数组。

所以React整体思路使用两轮遍历,第一轮处理要更新的节点,第二轮处理非更新的节点。

第一轮流程如下:

  1. 遍历newChildren,将newChildren[i]oldFiber比较,也就是上面的单节点比较,判断节点是否可复用。

  2. 如果可复用,i++,判断oldFiber.sibling是否可复用。

  3. 如果不能复用:

    1. key不同,跳出循环,第一轮结束;

    2. key相同,type不同,标记oldFiber为deletion,继续遍历。

  4. newChildren或者oldFiber遍历完,跳出遍历。

第一轮完成后,会有以下几种情况:

  • newChildren和oldFiber都没有遍历完,进行第二轮遍历;

  • newChildren遍历完,oldFiber没有遍历完,遍历剩下的oldFiber,添加标记,deletion;

if (newIdx === newChildren.length) {
  deleteRemainingChildren(returnFiber, oldFiber);
  return resultingFirstChild;
}
  • newChildren没有遍历完,oldFiber遍历完,遍历剩下的newChildren,添加标记,placement;

if (oldFiber === null) {
  for (; newIdx < newChildren.length; newIdx++) {
    const newFiber = createChild(returnFiber, newChildren[newIdx], lanes);
    if (newFiber === null) {
      continue;
    }
    lastPlacedIndex = placeChild(newFiber, lastPlacedIndex, newIdx);
    if (previousNewFiber === null) {
      resultingFirstChild = newFiber;
    } else {
      previousNewFiber.sibling = newFiber;
    }
    previousNewFiber = newFiber;
  }
  return resultingFirstChild;
}
  • newChildren和oldFiber都遍历完,此时直接进行update,diff结束。

第二轮遍历:

根据oldFiber创建map

function mapRemainingChildren(
    returnFiber: Fiber,
    currentFirstChild: Fiber,
): Map<string | number, Fiber> {
  const existingChildren: Map<string | number, Fiber> = new Map();
  let existingChild: null | Fiber = currentFirstChild;
  while (existingChild !== null) {
    if (existingChild.key !== null) {
      existingChildren.set(existingChild.key, existingChild);
    } else {
      existingChildren.set(existingChild.index, existingChild);
    }
    existingChild = existingChild.sibling;
  }
  return existingChildren;
}

接着遍历newChildren,根据map查找对应的节点,但是我们还需要知道节点是否移动。

我们需要lastPlacedIndex来标记最后一个可复用节点在oldFiber中的位置,用oldIndex指向遍历到的可复用节点,如果oldIndex < lastPlacedIndex,就要将可复用节点向右移动。

lastPlacedIndex初始为0,每遍历一个可复用的节点,如果oldIndex >= lastPlacedIndex,则lastPlacedIndex = oldIndex

completeWork

completeWork的工作就是根据不同的Fiber.tag进行不同的处理,会打一些标记(更新/删除/新增),也就是之前介绍的,Fiber节点中的这些属性:

this.effectTag = NoEffect;
this.subtreeTag = NoSubtreeEffect;
this.deletions = null;
this.nextEffect = null;

this.firstEffect = null;
this.lastEffect = null;

会生成形如 firstEffect -> nextEffect -> ... -> nextEffect -> lastEffect 的 effectList,保存了我们变化的内容。

最终会将 effectList 交给 commit 阶段去处理。

commit阶段

一些生命周期或者hooks(useEffect)将在commit阶段执行。

commit阶段可以分为三个部分:

  1. before mutation阶段(执行DOM操作前)

  2. mutation阶段(执行DOM操作)

  3. layout阶段(执行DOM操作后)

before mutation

do {
    // 触发useEffect回调与其他同步任务。由于这些任务可能触发新的渲染,所以这里要一直遍历执行直到没有任务
    flushPassiveEffects();
  } while (rootWithPendingPassiveEffects !== null);

  // root指 fiberRootNode
  // root.finishedWork指当前应用的rootFiber
  const finishedWork = root.finishedWork;

  // 凡是变量名带lane的都是优先级相关
  const lanes = root.finishedLanes;
  if (finishedWork === null) {
    return null;
  }
  root.finishedWork = null;
  root.finishedLanes = NoLanes;

  // 重置Scheduler绑定的回调函数
  root.callbackNode = null;
  root.callbackId = NoLanes;

  let remainingLanes = mergeLanes(finishedWork.lanes, finishedWork.childLanes);
  // 重置优先级相关变量
  markRootFinished(root, remainingLanes);

  // 清除已完成的discrete updates,例如:用户鼠标点击触发的更新。
  if (rootsWithPendingDiscreteUpdates !== null) {
    if (
      !hasDiscreteLanes(remainingLanes) &&
      rootsWithPendingDiscreteUpdates.has(root)
    ) {
      rootsWithPendingDiscreteUpdates.delete(root);
    }
  }

  // 重置全局变量
  if (root === workInProgressRoot) {
    workInProgressRoot = null;
    workInProgress = null;
    workInProgressRootRenderLanes = NoLanes;
  } else {
  }

  // 将effectList赋值给firstEffect
  // 由于每个fiber的effectList只包含他的子孙节点
  // 所以根节点如果有effectTag则不会被包含进来
  // 所以这里将有effectTag的根节点插入到effectList尾部
  // 这样才能保证有effect的fiber都在effectList中
  let firstEffect;
  if (finishedWork.effectTag > PerformedWork) {
    if (finishedWork.lastEffect !== null) {
      finishedWork.lastEffect.nextEffect = finishedWork;
      firstEffect = finishedWork.firstEffect;
    } else {
      firstEffect = finishedWork;
    }
  } else {
    // 根节点没有effectTag
    firstEffect = finishedWork.firstEffect;
  }

mutation

遍历effectList,针对其中每个节点调用commitMutationEffects函数,根据不同的标记(更新/删除/新增)进行不同的操作。

nextEffect = firstEffect;
do {
  try {
      commitMutationEffects(root, renderPriorityLevel);
    } catch (error) {
      invariant(nextEffect !== null, 'Should be working on an effect.');
      captureCommitPhaseError(nextEffect, error);
      nextEffect = nextEffect.nextEffect;
    }
} while (nextEffect !== null);
function commitMutationEffects(root: FiberRoot, renderPriorityLevel) {
  // 遍历effectList
  while (nextEffect !== null) {

    const effectTag = nextEffect.effectTag;

    // 根据 ContentReset effectTag重置文字节点
    if (effectTag & ContentReset) {
      commitResetTextContent(nextEffect);
    }

    // 更新ref
    if (effectTag & Ref) {
      const current = nextEffect.alternate;
      if (current !== null) {
        commitDetachRef(current);
      }
    }

    // 根据 effectTag 分别处理
    const primaryEffectTag =
      effectTag & (Placement | Update | Deletion | Hydrating);
    switch (primaryEffectTag) {
      // 插入DOM
      case Placement: {
        commitPlacement(nextEffect);
        nextEffect.effectTag &= ~Placement;
        break;
      }
      // 插入DOM 并 更新DOM
      case PlacementAndUpdate: {
        // 插入
        commitPlacement(nextEffect);

        nextEffect.effectTag &= ~Placement;

        // 更新
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      // SSR
      case Hydrating: {
        nextEffect.effectTag &= ~Hydrating;
        break;
      }
      // SSR
      case HydratingAndUpdate: {
        nextEffect.effectTag &= ~Hydrating;

        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      // 更新DOM
      case Update: {
        const current = nextEffect.alternate;
        commitWork(current, nextEffect);
        break;
      }
      // 删除DOM
      case Deletion: {
        commitDeletion(root, nextEffect, renderPriorityLevel);
        break;
      }
    }

    nextEffect = nextEffect.nextEffect;
  }
}

layout

此时已经渲染完成,会触发一些生命周期,能够拿到渲染完成后的结果了。

同样也是while循环遍历effectList

root.current = finishedWork;

nextEffect = firstEffect;
do {
  try {
    commitLayoutEffects(root, lanes);
  } catch (error) {
    invariant(nextEffect !== null, "Should be working on an effect.");
    captureCommitPhaseError(nextEffect, error);
    nextEffect = nextEffect.nextEffect;
  }
} while (nextEffect !== null);

nextEffect = null;
function commitLayoutEffects(root: FiberRoot, committedLanes: Lanes) {
  while (nextEffect !== null) {
    const effectTag = nextEffect.effectTag;

    // 调用生命周期钩子和hook
    if (effectTag & (Update | Callback)) {
      const current = nextEffect.alternate;
      commitLayoutEffectOnFiber(root, current, nextEffect, committedLanes);
    }

    // 赋值ref
    if (effectTag & Ref) {
      commitAttachRef(nextEffect);
    }

    nextEffect = nextEffect.nextEffect;
  }
}

优先级

React出于对用户体验的考量,对于不同的交互分配了不同的优先级,主要有以下几种优先级:

switch (priorityLevel) {
  case ImmediatePriority:
    // Times out immediately
    timeout = -1;
    break;
  case UserBlockingPriority:
    // Eventually times out
    timeout = userBlockingPriorityTimeout;
    break;
  case IdlePriority:
    // Never times out
    timeout = maxSigned31BitInt;
    break;
  case LowPriority:
    // Eventually times out
    timeout = lowPriorityTimeout;
    break;
  case NormalPriority:
  default:
    // Eventually times out
    timeout = normalPriorityTimeout;
    break;
}

React会调用Scheduler中的runWithPriority方法,根据优先级排列,并且通过定时器去触发对应的事件:

function unstable_runWithPriority<T>(
  priorityLevel: PriorityLevel,
  eventHandler: () => T,
): T {
  switch (priorityLevel) {
    case ImmediatePriority:
    case UserBlockingPriority:
    case NormalPriority:
    case LowPriority:
    case IdlePriority:
      break;
    default:
      priorityLevel = NormalPriority;
  }

  var previousPriorityLevel = currentPriorityLevel;
  currentPriorityLevel = priorityLevel;

  try {
    return eventHandler();
  } finally {
    currentPriorityLevel = previousPriorityLevel;
  }
}

lane模型

lane模型是React中表示优先级的机制,它使用31位二进制,位数越小的优先级越高,某些相邻的位具有相同的优先级。

export const NoLanes: Lanes = /*                        / 0b0000000000000000000000000000000;
export const NoLane: Lane = /                          / 0b0000000000000000000000000000000;

export const SyncLane: Lane = /                        / 0b0000000000000000000000000000001;
export const SyncBatchedLane: Lane = /                 / 0b0000000000000000000000000000010;

export const InputDiscreteHydrationLane: Lane = /      / 0b0000000000000000000000000000100;
const InputDiscreteLanes: Lanes = /                    / 0b0000000000000000000000000011000;

const InputContinuousHydrationLane: Lane = /           / 0b0000000000000000000000000100000;
const InputContinuousLanes: Lanes = /                  / 0b0000000000000000000000011000000;

export const DefaultHydrationLane: Lane = /            / 0b0000000000000000000000100000000;
export const DefaultLanes: Lanes = /                   / 0b0000000000000000000111000000000;

const TransitionHydrationLane: Lane = /                / 0b0000000000000000001000000000000;
const TransitionLanes: Lanes = /                       / 0b0000000001111111110000000000000;

const RetryLanes: Lanes = /                            / 0b0000011110000000000000000000000;

export const SomeRetryLane: Lanes = /                  / 0b0000010000000000000000000000000;

export const SelectiveHydrationLane: Lane = /          / 0b0000100000000000000000000000000;

const NonIdleLanes = /                                 / 0b0000111111111111111111111111111;

export const IdleHydrationLane: Lane = /               / 0b0001000000000000000000000000000;
const IdleLanes: Lanes = /                             / 0b0110000000000000000000000000000;

export const OffscreenLane: Lane = /                   */ 0b1000000000000000000000000000000;

同时,优先级还具有“批”的概念,例如Transition中有多个连续的1,表示占用多条通道。

使用了二进制位,就会方便我们的优先级计算,因为在计算机中位运算是很快的。

// 判断a b是否有交集
export function includesSomeLane(a: Lanes | Lane, b: Lanes | Lane) {
  return (a & b) !== NoLanes;
}

// 计算b这个lanes是否是a对应的lanes的子集,只需要判断a与b按位与的结果是否为b:
export function isSubsetOfLanes(set: Lanes, subset: Lanes | Lane) {
  return (set & subset) === subset;
}

// 将两个lane或lanes的位合并只需要执行按位或操作:
export function mergeLanes(a: Lanes | Lane, b: Lanes | Lane): Lanes {
  return a | b;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值