React 源码 - batchedUpdates

99 阅读1分钟
// 老版本: react 触发一次 render
// 新版本: react 触发一次 render
onClick = () => {
  this.setState({
    num: this.state.num + 1
  })

  this.setState({
    num: this.state.num + 1
  })
}


// 老版本: react 触发两次 render
// 新版本: react 触发一次 render
onClick = () => {
  setTimeout(() => {
    this.setState({
      num: this.state.num + 1
    })

    this.setState({
      num: this.state.num + 1
    })
  })
}
旧版本更新机制
  • 整个过程是同步执行的,如果改为异步调用 setTimeout
  • 回调函数执行时形成的 executionContext 在setTimeout 中拿不到
// 为 onClick 回调函数
export function batchedUpdates<A, R>(fn: A => R, a: A): R {
  const prevExecutionContext = executionContext;
  executionContext |= BatchedContext;
  try {
    // 1、执行 fn 函数,不会立刻 render
    // 2、如果函数内被 setTimeout 包裹,等 setTimeout 开始执行的时候就获取不到 BatchedContext
    return fn(a);
  } finally {

    executionContext = prevExecutionContext;
    if (executionContext === NoContext) {
      resetRenderTimer();

      // 最后一块 render
      flushSyncCallbacksOnlyInLegacyMode();
    }
  }
}
新版本更新机制
  • 原理是对比 lane ,如果前后两次的 lane 相同直接 return
// 首次
setState
    |
    |
    V
enqueueSetState(inst, payload, callback)
- const fiber = getInstance(inst); // 调用第一个 setState
- const lane = requestUpdateLane(fiber); // 获取当前可用的 lane
- const update = createUpdate(eventTime, lane);
    |
    |
    V

const root = enqueueUpdate(fiber, update, lane);
    |
    |
    V

scheduleUpdateOnFiber(root, fiber, lane, eventTime); 
- ensureRootIsScheduled(root, eventTime); // 调度当前应用根节点 render
- - const existingCallbackNode = root.callbackNode; // 首次调用不存在
- - scheduleCallback // 开始调度回调函数
- - performConcurrentWorkOnRoot // render 函数的起点
- - - prepareFreshStack // 创建一个新的 Stack
- - - - workInProgressRoot = root;
- - - - const rootWorkInProgress = createWorkInProgress(root.current, null); // 依次向下创建tree结构
    |
    |
    V

// 第二次
setState
...
...
const existingCallbackNode = root.callbackNode; // 第二次存在被调用的对象 performConcurrentWorkOnRoot
existingCallbackNode !== null // 不会继续调度,不再执行 performConcurrentWorkOnRoot