[React 源码] React 18.2 - 批量更新 [0.7k 字 - 阅读时长2min]

212 阅读2分钟

批量更新效果展示:

delete.gif

const App = () => {
  const [number, setNumber] = React.useState(1);
  console.log("App 组件, 渲染");
  const handleClick = () => {
    setNumber((pre) => (pre += 1));
    setNumber((pre) => (pre += 1));
    setNumber((pre) => (pre += 1));
  };
  return (
    <div>
      {number}
      <div
        onClick={handleClick}
      >
        批量更新
      </div>
    </div>
  );
};

我们发现,触发三次 setNumber, App 组件 只渲染一次,并且 number 值可以更新为 4。

批量更新原理

第一:当点击的时候, 触发第一次 setNumber, 调用的是 dispatchSetState, 在 该函数中,将 update 对象 加入到 hook.queue.pending 循环队列当中,然后 调用了熟悉的 scheduleUpdateOnFiber 函数,从根节点开始调度更新。

const dispatchSetState =  () => {
 if (root !== null) {
      const eventTime = requestEventTime();
      scheduleUpdateOnFiber(root, fiber, lane, eventTime);
      entangleTransitionUpdate(root, queue, lane);
    }
}

第二:scheduleUpdateOnFiber 函数 中 调用了 ensureRootIsScheduled 函数。

export function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
  eventTime: number
) {
  ensureRootIsScheduled 函数。(root, eventTime);
}

第三: ensureRootIsScheduled 函数中,将 performConcurrentWorkOnRoot 交给 Scheduler, 进行异步调用。

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  newCallbackNode = scheduleCallback(
    schedulerPriorityLevel,
    performConcurrentWorkOnRoot.bind(null, root)
  );

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

第四:异步调用之后,又同步来到了 第二个 setNumber 函数调用。调用的是 dispatchSetState, 在 该函数中,将 update 对象 加入到 hook.queue.pending 循环队列当中,然后 调用了熟悉的 scheduleUpdateOnFiber 函数,从根节点开始调度更新。

const dispatchSetState =  () => {
 if (root !== null) {
      const eventTime = requestEventTime();
      scheduleUpdateOnFiber(root, fiber, lane, eventTime);
      entangleTransitionUpdate(root, queue, lane);
    }
}

第五:scheduleUpdateOnFiber 函数 中 调用了 ensureRootIsScheduled 函数。

export function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
  eventTime: number
) {
  ensureRootIsScheduled 函数。(root, eventTime);
}

第六: ensureRootIsScheduled 函数中,判断新的更新优先级 和 当前已经存在的优先级相等,则不会进行调度更新,进行返回。

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  if (existingCallbackPriority === newCallbackPriority) {
    return;
  }
}

第七:异步调用之后,又同步来到了 第三个 setNumber 函数调用。调用的是 dispatchSetState, 在 该函数中,将 update 对象 加入到 hook.queue.pending 循环队列当中,然后 调用了熟悉的 scheduleUpdateOnFiber 函数,从根节点开始调度更新。

const dispatchSetState =  () => {
 if (root !== null) {
      const eventTime = requestEventTime();
      scheduleUpdateOnFiber(root, fiber, lane, eventTime);
      entangleTransitionUpdate(root, queue, lane);
    }
}

第八:scheduleUpdateOnFiber 函数 中 调用了 ensureRootIsScheduled 函数。

export function scheduleUpdateOnFiber(
  root: FiberRoot,
  fiber: Fiber,
  lane: Lane,
  eventTime: number
) {
  ensureRootIsScheduled 函数。(root, eventTime);
}

第九: ensureRootIsScheduled 函数中,判断新的更新优先级 和 当前已经存在的优先级相等,则不会进行调度更新,进行返回。

function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
  if (existingCallbackPriority === newCallbackPriority) {
    return;
  }
}

第十:三次 setNumber() 同步代码执行完毕后,空闲时间时,开始执行由第一次更新产生的调度任务,从根节点开始,深度优先调度 fiber 节点,进行更新。更新到 App 组件时,调用 useState, 更新时调用的 useState, 是 updateReducer 函数,在 updateReducer 函数中,根据三次更新产生的更新队列,计算出新状态 4 -> 提交。

自此 批量更新结束。