likes
comments
collection
share

React-Fiber 手写实现,源码理解(3)

作者站长头像
站长
· 阅读数 16

react-reconciler核心逻辑理解并手写

想了很久该怎么写,才能写出来让大家看懂,就我们先抛开复杂的车道优先级和调度优先级以及调度等级以及react大量的时间操作等到后面分析18源码的时候一起讲。就从最简单的开始手写fiber-dom。

1.react构建fiber核心代码

对应源码位于ReactFiberWorkLoop.js

1.1 入口

在全局变量中有executionContext, 代表渲染期间执行上下文, 它也是一个二进制表示的变量, 通过位运算进行操作. 在源码中一共定义了 8 种执行栈:

type ExecutionContext = number;
export const NoContext = /*             */ 0b0000000;
const BatchedContext = /*               */ 0b0000001;
const EventContext = /*                 */ 0b0000010;
const DiscreteEventContext = /*         */ 0b0000100;
const LegacyUnbatchedContext = /*       */ 0b0001000;
const RenderContext = /*                */ 0b0010000;
const CommitContext = /*                */ 0b0100000;

这里我们只分析Legacy模式(对启动模式不清楚的可以看看开篇), Legacy模式下的首次更新, 会直接进行构建,然后经过提交到页面上,并不会进入去注册调度任务。

接下来我们看核心的源码

export function scheduleUpdateOnFiber(
  fiber: Fiber,
  lane: Lane,
  eventTime: number,
) {
  if (lane === SyncLane) {
    // legacy模式
    if (
      // 首次无上下文也无渲染上下文
      (executionContext & LegacyUnbatchedContext) !== NoContext &&
      (executionContext & (RenderContext | CommitContext)) === NoContext
    ) {
      // 初次更新
      performSyncWorkOnRoot(root);
    } else {
      // 后续的更新,注册调度任务,并传入时间但这个时间在react中多是用来做统计的暂时可以不管
      ensureRootIsScheduled(root, eventTime);
    }
  } 
}

在 render 过程中, 每一个阶段都会改变executionContext(render 之前, 会设置executionContext |= RenderContext; commit 之前, 会设置executionContext |= CommitContext), 假设在render过程中再次发起更新(如在UNSAFE_componentWillReceiveProps生命周期中调用setState)则可通过executionContext来判断当前的render状态.

1.2 performSyncWorkOnRoot

上面我们已经看到了入口的函数performSyncWorkOnRootensureRootIsScheduled接下来我们分析这2个函数是用来干啥的。

function performSyncWorkOnRoot(root) {
  let lanes;
  let exitStatus;
  // workInProgressRoot当前构建的任务
  if (
    root === workInProgressRoot &&
    includesSomeLane(root.expiredLanes, workInProgressRootRenderLanes)
  ) {
    // 初次构建的时候从root节点开始, 形成一个完成的fiberdom树
    lanes = workInProgressRootRenderLanes;
    exitStatus = renderRootSync(root, lanes);
  } else {
    // 1. 获取本次render的优先级, 初次构造返回 NoLanes
    lanes = getNextLanes(root, NoLanes);
    // 2. 从root节点开始, 至上而下更新,形成一个完成的fiberdom树
    exitStatus = renderRootSync(root, lanes);
  }

  // 将最新的fiber树挂载到root.finishedWork节点上
  const finishedWork: Fiber = (root.current.alternate: any);
  root.finishedWork = finishedWork;
  root.finishedLanes = lanes;
  // 进入commit阶段,渲染到页面上
  commitRoot(root);
}

实际我们看到不管是初次构建还是后续更新都会走到renderRootSync上去构建fiber树,那它又做了什么那

function renderRootSync(root: FiberRoot, lanes: Lanes) {
  const prevExecutionContext = executionContext;
  executionContext |= RenderContext;
  // 如果fiberRoot变动, 或者update.lane变动,update(链表我们后面讲)
  if (workInProgressRoot !== root || workInProgressRootRenderLanes !== lanes) {
    // 刷新栈帧
    prepareFreshStack(root, lanes);
  }
  do {
    try {
      // 核心代码循环构建fiber
      workLoopSync();
      break;
    } catch (thrownValue) {
      handleError(root, thrownValue);
    }
  } while (true);
  executionContext = prevExecutionContext;
  // 重置全局变量, 表明render结束
  workInProgressRoot = null;
  workInProgressRootRenderLanes = NoLanes;
  return workInProgressRootExitStatus;
}

如果是初读源码我们可以只关心一个地方那就是workLoopSync,当然这是在legacy模式下,concurrent模式下走的是workLoopConcurrent去实现时间分片和循环可中断。下一步我们就可以看workLoopSync

function workLoopSync() {
  while (workInProgress !== null) {
    performUnitOfWork(workInProgress);
  }
}

这里就相当于把循坏构建fiberDom树了,而前面备注中也写到了workInProgress指向当前构建的任务或者说节点更适合一些,来到构建fiberDom树的performUnitOfWork.

function performUnitOfWork(unitOfWork: Fiber): void {
  // 指向当前页面的`fiber`节点. 初次构造时, 页面还未渲染, 就是空的
  const current = unitOfWork.alternate;
  let next;
  if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
    startProfilerTimer(unitOfWork);
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
    stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
  } else {
    //构建出最终的fiberDom,并且返回dom结构的chilrden处理之后的child
    next = beginWork(current, unitOfWork, subtreeRenderLanes);
  }
  unitOfWork.memoizedProps = unitOfWork.pendingProps;
  if (next === null) {
    // 没新节点的时候就可以提交洛到这的话
    completeUnitOfWork(unitOfWork);
  } else {
    workInProgress = next;
  }
  ReactCurrentOwner.current = null;
}

进入beginWork

function beginWork(
  current: Fiber | null,
  workInProgress: Fiber,
  renderLanes: Lanes,
): Fiber | null {
  // 忽略代码
  // 这里是对于所有fiber类型的不同处理,我们关心不同tag怎么处理的就行了,等下手写的时候写简单一点
  switch (workInProgress.tag) {
    case IndeterminateComponent: {
      return mountIndeterminateComponent(
        current,
        workInProgress,
        workInProgress.type,
        renderLanes,
      );
    }
    case LazyComponent: {
      const elementType = workInProgress.elementType;
      return mountLazyComponent(
        current,
        workInProgress,
        elementType,
        updateLanes,
        renderLanes,
      );
    }
    case FunctionComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateFunctionComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    case ClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return updateClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    case HostRoot:
      return updateHostRoot(current, workInProgress, renderLanes);
    case HostComponent:
      return updateHostComponent(current, workInProgress, renderLanes);
    case HostText:
      return updateHostText(current, workInProgress);
    case SuspenseComponent:
      return updateSuspenseComponent(current, workInProgress, renderLanes);
    case HostPortal:
      return updatePortalComponent(current, workInProgress, renderLanes);
    case ForwardRef: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === type
          ? unresolvedProps
          : resolveDefaultProps(type, unresolvedProps);
      return updateForwardRef(
        current,
        workInProgress,
        type,
        resolvedProps,
        renderLanes,
      );
    }
    case Fragment:
      return updateFragment(current, workInProgress, renderLanes);
    case Mode:
      return updateMode(current, workInProgress, renderLanes);
    case Profiler:
      return updateProfiler(current, workInProgress, renderLanes);
    case ContextProvider:
      return updateContextProvider(current, workInProgress, renderLanes);
    case ContextConsumer:
      return updateContextConsumer(current, workInProgress, renderLanes);
    case MemoComponent: {
      const type = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      // Resolve outer props first, then resolve inner props.
      let resolvedProps = resolveDefaultProps(type, unresolvedProps);
      if (__DEV__) {
        if (workInProgress.type !== workInProgress.elementType) {
          const outerPropTypes = type.propTypes;
          if (outerPropTypes) {
            checkPropTypes(
              outerPropTypes,
              resolvedProps, // Resolved for outer only
              'prop',
              getComponentName(type),
            );
          }
        }
      }
      resolvedProps = resolveDefaultProps(type.type, resolvedProps);
      return updateMemoComponent(
        current,
        workInProgress,
        type,
        resolvedProps,
        updateLanes,
        renderLanes,
      );
    }
    case SimpleMemoComponent: {
      return updateSimpleMemoComponent(
        current,
        workInProgress,
        workInProgress.type,
        workInProgress.pendingProps,
        updateLanes,
        renderLanes,
      );
    }
    case IncompleteClassComponent: {
      const Component = workInProgress.type;
      const unresolvedProps = workInProgress.pendingProps;
      const resolvedProps =
        workInProgress.elementType === Component
          ? unresolvedProps
          : resolveDefaultProps(Component, unresolvedProps);
      return mountIncompleteClassComponent(
        current,
        workInProgress,
        Component,
        resolvedProps,
        renderLanes,
      );
    }
    case SuspenseListComponent: {
      return updateSuspenseListComponent(current, workInProgress, renderLanes);
    }
    case FundamentalComponent: {
      if (enableFundamentalAPI) {
        return updateFundamentalComponent(current, workInProgress, renderLanes);
      }
      break;
    }
    case ScopeComponent: {
      if (enableScopeAPI) {
        return updateScopeComponent(current, workInProgress, renderLanes);
      }
      break;
    }
    case Block: {
      if (enableBlocksAPI) {
        const block = workInProgress.type;
        const props = workInProgress.pendingProps;
        return updateBlock(current, workInProgress, block, props, renderLanes);
      }
      break;
    }
    case OffscreenComponent: {
      return updateOffscreenComponent(current, workInProgress, renderLanes);
    }
    case LegacyHiddenComponent: {
      return updateLegacyHiddenComponent(current, workInProgress, renderLanes);
    }
  }
}

举个例子updateHostRoot

function updateHostRoot(current, workInProgress, renderLanes) {
  // 1. 状态计算, 更新整合到 workInProgress.memoizedState中来
  const updateQueue = workInProgress.updateQueue;
  const nextProps = workInProgress.pendingProps;
  const prevState = workInProgress.memoizedState;
  const prevChildren = prevState !== null ? prevState.element : null;
  cloneUpdateQueue(current, workInProgress);
  // 遍历updateQueue.shared.pending, 提取有足够优先级的update对象, 计算出最终的状态 workInProgress.memoizedState
  processUpdateQueue(workInProgress, nextProps, null, renderLanes);
  const nextState = workInProgress.memoizedState;
  // 2. 获取下级`ReactElement`对象
  const nextChildren = nextState.element;
  const root: FiberRoot = workInProgress.stateNode;
  if (root.hydrate && enterHydrationState(workInProgress)) {
    // ...服务端渲染相关, 此处省略
  } else {
    // 3. 根据`ReactElement`对象, 调用`reconcileChildren`生成`Fiber`子节点(只生成`次级子节点`)
    reconcileChildren(current, workInProgress, nextChildren, renderLanes);
  }
  // 返回child进行下一次循坏
  return workInProgress.child;
}

到这基本上整个初始化构建过程就已经搞完了

1.3 ensureRootIsScheduled

注册调度就非常的简单了

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  //判断是否注册新的调度
  const existingCallbackNode = root.callbackNode;
  const nextLanes = getNextLanes(
    root,
    root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
  );
  const newCallbackPriority = returnNextLanesPriority();
  // Schedule a new callback.
  let newCallbackNode;
  if (newCallbackPriority === SyncLanePriority) {
    // 注册调度走到scheduler去
    newCallbackNode = scheduleSyncCallback(
      performSyncWorkOnRoot.bind(null, root),
    );
  } else if (newCallbackPriority === SyncBatchedLanePriority) {
    // 批处理注册调度走到scheduler去
    newCallbackNode = scheduleCallback(
      ImmediateSchedulerPriority,
      performSyncWorkOnRoot.bind(null, root),
    );
  } else {
    // 并发注册调度走到scheduler去
    const schedulerPriorityLevel = lanePriorityToSchedulerPriority(
      newCallbackPriority,
    );
    newCallbackNode = scheduleCallback(
      schedulerPriorityLevel,
      performConcurrentWorkOnRoot.bind(null, root),
    );
  }

  root.callbackPriority = newCallbackPriority;
  root.callbackNode = newCallbackNode;
}

说来说去我们就只关心一个事情newCallbackNode = scheduleCallback( ImmediateSchedulerPriority, performSyncWorkOnRoot.bind(null, root), );他这里把回调函数给到调度中心并绑定节点,等待调度执行之后重复上面1.2的所有操作,我们下一节分析调度是怎么做的。

2.手写

/*
 * @Description:
 * @Date: 2022-11-23 22:44:29
 */
import { arrified, getRoot, getTag, createStateNode } from '../until';
import { commitAllWork } from './commit';
import { scheduleCallback } from '../scheduler';
let first = 1;
let subTask = null;
let pendingCommit = null;
// 构建最外层的fiber对象
function createOutFiber(jsx, root) {
  const task = {
    root,
    props: {
      children: jsx
    }
  };
  let outFiber;
  if (task.from === 'class_component') {
    const root = getRoot(task.instance);
    task.instance.__fiber.partialState = task.partialState;
    outFiber = {
      props: root.props,
      stateNode: root.stateNode,
      tag: 'host_root',
      effects: [],
      child: null,
      alternate: root
    };
    return outFiber;
  }
  outFiber = {
    props: task.props,
    stateNode: task.root,
    tag: 'host_root',
    effects: [],
    child: null,
    alternate: task.root.__rootFiberContainer
  };
  return outFiber;
}

function reconcileChildren(fiber, children) {
  /**
   * children 可能对象 也可能是数组
   * 将children 转换成数组
   */
  const arrifiedChildren = arrified(children);
  /**
   * 循环 children 使用的索引
   */
  let index = 0;
  /**
   * children 数组中元素的个数
   */
  let numberOfElements = arrifiedChildren.length;
  /**
   * 循环过程中的循环项 就是子节点的 virtualDOM 对象
   */
  let element = null;
  /**
   * 子级 fiber 对象
   */
  let newFiber = null;
  /**
   * 上一个兄弟 fiber 对象
   */
  let prevFiber = null;

  let alternate = null;
  if (fiber.alternate && fiber.alternate.child) {
    alternate = fiber.alternate.child;
  }
  console.log(arrifiedChildren);
  while (index < numberOfElements || alternate) {
    /**
     * 子级 virtualDOM 对象
     */
    element = arrifiedChildren[index];

    if (!element && alternate) {
      /**
       * 删除操作
       */
      alternate.effectTag = 'delete';
      fiber.effects.push(alternate);
    } else if (element && alternate) {
      /**
       * 更新
       */
      newFiber = {
        type: element.type,
        props: element.props,
        tag: getTag(element),
        effects: [],
        effectTag: 'update',
        parent: fiber,
        alternate
      };
      if (element.type === alternate.type) {
        /**
         * 类型相同
         */
        newFiber.stateNode = alternate.stateNode;
      } else {
        /**
         * 类型不同
         */
        newFiber.stateNode = createStateNode(newFiber);
      }
    } else if (element && !alternate) {
      /**
       * 初始渲染
       */
      /**
       * 子级 fiber 对象
       */
      newFiber = {
        type: element.type,
        props: element.props,
        tag: getTag(element),
        effects: [],
        effectTag: 'placement',
        parent: fiber
      };
      /**
       * 为fiber节点添加DOM对象或组件实例对象
       */
      newFiber.stateNode = createStateNode(newFiber);
      newFiber.stateNode = createStateNode(newFiber);
    }

    if (index === 0) {
      fiber.child = newFiber;
    } else if (element) {
      prevFiber.sibling = newFiber;
    }

    if (alternate && alternate.sibling) {
      alternate = alternate.sibling;
    } else {
      alternate = null;
    }

    // 更新
    prevFiber = newFiber;
    index++; //保存构建fiber节点的索引,等待事件后通过索引再次进行构建
  }
}

function workLoopSync() {
  while (subTask) {
    subTask = performUnitOfWork(subTask);
  }
  if (pendingCommit) {
    first++;
    commitAllWork(pendingCommit);
  }
}

// 构建子集的fiber对象的任务单元
function performUnitOfWork(fiber) {
  reconcileChildren(fiber, fiber.props.children);
  /**
   * 如果子级存在 返回子级
   * 将这个子级当做父级 构建这个父级下的子级
   */
  if (fiber.child) {
    return fiber.child;
  }

  /**
   * 如果存在同级 返回同级 构建同级的子级
   * 如果同级不存在 返回到父级 看父级是否有同级
   */
  let currentExecutelyFiber = fiber;
  while (currentExecutelyFiber.parent) {
    currentExecutelyFiber.parent.effects =
      currentExecutelyFiber.parent.effects.concat(
        currentExecutelyFiber.effects.concat([currentExecutelyFiber])
      );

    if (currentExecutelyFiber.sibling) {
      return currentExecutelyFiber.sibling;
    }
    currentExecutelyFiber = currentExecutelyFiber.parent;
  }
  pendingCommit = currentExecutelyFiber;
  console.log(pendingCommit);
}

// 源码地址 https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L674-L736
function ensureRootIsScheduled(fiber) {
  //这里我们可以直接走到注册调度任务,暂时我们分析的是Legacy模式,Concurrent模式实现的performConcurrentWorkOnRoot实现的可中断渲染可以以后实现
  let newCallbackNode;

  //接下来就可以直接走到调度中心去
  newCallbackNode = scheduleCallback(workLoopSync);
}

// 源码地址 https://github.com/facebook/react/blob/v17.0.2/packages/react-reconciler/src/ReactFiberWorkLoop.old.js#L517-L619
function scheduleUpdateOnFiber(fiber) {
  subTask = fiber;
  if (!first) {
    //对应暂无render上下文
    // 对于初次构建来说我们直接进行`fiber构造`.
    workLoopSync();
  } else {
    //对于后续更新以及操作都选择去注册调度任务
    ensureRootIsScheduled(subTask);
  }
}
export function render(jsx, root) {
  const outFiber = createOutFiber(jsx, root);
  scheduleUpdateOnFiber(outFiber);
}

通过二叉树,然后我们就快乐的简单实现了fiberDom的过程~~~

总结

比较多工程化和基础的东西,还没完善,有兴趣的可以gitbub拉下来试试