lz-lee / React-Source-Code

React源码阅读笔记
11 stars 0 forks source link

setState和batchUpdates、scheduleWork #3

Open lz-lee opened 5 years ago

lz-lee commented 5 years ago

setState 和 batchUpdates

setState 和 forceUpdate

来源

Component 组件原型上的方法,他们调用的 enqueueSetStateenqueueForceUpdate 都是来自不同平台创建的 updater 对象上的,运行在浏览器中的 updater 对象来自 ReactFiberClassComponent.js 里的 classComponentUpdater 对象上。

// ReactBaseClasses.js

function Component(props, context, updater) {
  this.props = props;
  this.context = context;
  // If a component has string refs, we will assign a different object later.
  this.refs = emptyObject;
  // We initialize the default updater but the real one gets injected by the
  // renderer.
  // 作为参数让不同的平台来控制属性更新的方式
  this.updater = updater || ReactNoopUpdateQueue;
}

Component.prototype.setState = function(partialState, callback) {
  // 仅仅调用了 updater 上的方法 updater 是初始化的第三个参数的实例属性,跟平台相关
  // 这里的 this 即对应组件(fiber 节点)
  this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
Component.prototype.forceUpdate = function(callback) {
  this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};

classComponentUpdater

const classComponentUpdater = {
  isMounted,
  enqueueSetState(inst, payload, callback) {
    // 从instanceMap中获取Fiber对象, inst 为调用setState的this,即那个组件
    const fiber = ReactInstanceMap.get(inst);
    // 创建当前时间
    const currentTime = requestCurrentTime();
    // 计算优先级时间
    const expirationTime = computeExpirationForFiber(currentTime, fiber);
    // 创建update对象
    const update = createUpdate(expirationTime);
    // 设置payload
    update.payload = payload;
    if (callback !== undefined && callback !== null) {
      if (__DEV__) {
        warnOnInvalidCallback(callback, 'setState');
      }
      // 设置callback
      update.callback = callback;
    }
    // 将 update 对象加入 fiber 的 updateQueue 中
    enqueueUpdate(fiber, update);
    // 执行调度更新
    scheduleWork(fiber, expirationTime);
  },
  enqueueReplaceState(inst, payload, callback) {
    // ...
  },
  enqueueForceUpdate(inst, callback) {
    // ...
  },
};

scheduleWork 开始调度

核心功能

scheduleWork

每一次进入调度队列的只有 FiberRoot 对象, 更新也是从 FiberRoot 对象上开始的。

function scheduleWork(fiber: Fiber, expirationTime: ExpirationTime) {
  // 找到 FiberRoot,并更新 FiberTree 上所有 Fiber 节点的expirationTime
  const root = scheduleWorkToRoot(fiber, expirationTime);
  // 没有 FiberRoot 返回
  if (root === null) {
    return;
  }

  if (
    !isWorking && // 没有执行渲染, isWorking 表示有任务正在进行中
    nextRenderExpirationTime !== NoWork && // 任务是个异步的,执行到一半了,把执行权交还给浏览器执行
    expirationTime < nextRenderExpirationTime // 新的任务优先级高于现在的任务
  ) {
    // 新的高优先级的任务打断了老的低优先级任务,优先执行高优先级的任务
    // This is an interruption. (Used for performance tracking.)
    // 用来记录
    interruptedBy = fiber;
    // 重置并回退之前更新过的state
    resetStack();
  }
  markPendingPriorityLevel(root, expirationTime);
  if (
    // If we're in the render phase, we don't need to schedule this root
    // for an update, because we'll do it before we exit...
    // 没有正在工作, isWorking 和 isCommitting 对应两个不同阶段,isWorking包含isCommitting 和 rendering, 简单说就是要么处于没有 work 的状态,要么只能在 commit 阶段
    !isWorking ||
    // 正在提交,也就是更新dom 树的渲染阶段
    isCommitting ||
    // ...unless this is a different root than the one we're rendering.
    // 不同的 root,一般不存在不同
    nextRoot !== root
  ) {
    // 调用了 markPendingPriorityLevel 后 rootExpirationTime 不一定是传入进来的 expirationTime
    const rootExpirationTime = root.expirationTime;
    // 开始调度
    requestWork(root, rootExpirationTime);
  }
  // componentWillUpdate、componentDidUpdate 函数里调用 setState,死循环。。
  if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {
    // Reset this back to zero so subsequent updates don't throw.
    nestedUpdateCount = 0;
    invariant(
      false,
      'Maximum update depth exceeded. This can happen when a ' +
        'component repeatedly calls setState inside ' +
        'componentWillUpdate or componentDidUpdate. React limits ' +
        'the number of nested updates to prevent infinite loops.',
    );
  }
}

scheduleWorkToRoot

function scheduleWorkToRoot(fiber: Fiber, expirationTime): FiberRoot | null {
  // Update the source fiber's expiration time
  if (
    // 没有更新操作
    fiber.expirationTime === NoWork ||
    // 之前有更新产生且没完成,但当前fiber更新的优先级低于当前的更新
    fiber.expirationTime > expirationTime
  ) {
    // 设置成高优先级的 expirationTime
    fiber.expirationTime = expirationTime;
  }
  // 同理更新  alternate 的expirationTime
  let alternate = fiber.alternate;
  if (
    alternate !== null &&
    (alternate.expirationTime === NoWork ||
      alternate.expirationTime > expirationTime)
  ) {
    alternate.expirationTime = expirationTime;
  }
  // Walk the parent path to the root and update the child expiration time.
  // return 即父节点
  let node = fiber.return;
  let root = null;
  // RootFiber 的 return 才为null
  if (node === null && fiber.tag === HostRoot) {
    // 返回 FiberRoot
    root = fiber.stateNode;
  } else {
    // 循环查找 RootFiber 对象
    while (node !== null) {
      alternate = node.alternate;
      // childExpirationTime 是Fiber子树当中优先级最高的 expirationTime
      // 如果符合条件,将childExpirationTime设置成更高优先级的更新
      if (
        node.childExpirationTime === NoWork ||
        node.childExpirationTime > expirationTime
      ) {
        node.childExpirationTime = expirationTime;
        if (
          alternate !== null &&
          (alternate.childExpirationTime === NoWork ||
            alternate.childExpirationTime > expirationTime)
        ) {
          alternate.childExpirationTime = expirationTime;
        }
      } else if (
        alternate !== null &&
        (alternate.childExpirationTime === NoWork ||
          alternate.childExpirationTime > expirationTime)
      ) {
        alternate.childExpirationTime = expirationTime;
      }
      // 找到 RootFiber 结束循环
      if (node.return === null && node.tag === HostRoot) {
        root = node.stateNode;
        break;
      }
      // 继续循环
      node = node.return;
    }
  }
  return root;
}

resetStack

function resetStack() {
  // nextUnitOfWork 是用来记录下一个要更新的节点
  if (nextUnitOfWork !== null) {
    // 向上找被打断的任务
    let interruptedWork = nextUnitOfWork.return;
    while (interruptedWork !== null) {
      // 退回状态,因为更高优先级的任务也是从头更新的,避免更新过的组件 state 错乱
      unwindInterruptedWork(interruptedWork);
      interruptedWork = interruptedWork.return;
    }
  }

  // 重置初始值,进行新的优先级任务更新
  nextRoot = null;
  nextRenderExpirationTime = NoWork;
  nextLatestAbsoluteTimeoutMs = -1;
  nextRenderDidError = false;
  nextUnitOfWork = null;
}

requestWork

function requestWork(root: FiberRoot, expirationTime: ExpirationTime) {
  // 将当前 FiberRoot 优先级设置为最高
  addRootToSchedule(root, expirationTime);
  // isRendering表示调度已经执行了,循环开始了,render 阶段不需要重新 requestWork 了
  if (isRendering) {
    // Prevent reentrancy. Remaining work will be scheduled at the end of
    // the currently rendering batch.
    return;
  }
  // 批量处理相关
  // 调用 setState 时在 enqueueUpdates 前 batchedUpdates 会把 isBatchingUpdates 设置成 true, 和事件系统有关
  // 为 true 则为调用 performSyncWork,则没有调度更新
  if (isBatchingUpdates) {
    // Flush work at the end of the batch.
    if (isUnbatchingUpdates) {
      // ...unless we're inside unbatchedUpdates, in which case we should
      // flush it now.
      nextFlushedRoot = root;
      nextFlushedExpirationTime = Sync;
      performWorkOnRoot(root, Sync, true);
    }
    return;
  }

  // TODO: Get rid of Sync and use current time?
  // 同步更新
  if (expirationTime === Sync) {
    performSyncWork();
  } else {
  // 异步调度,独立的 react 模块包,利用浏览器有空闲的时候进行执行,设置 deadline 在此之前执行
    scheduleCallbackWithExpirationTime(root, expirationTime);
  }
}

addRootToSchedule

function addRootToSchedule(root: FiberRoot, expirationTime: ExpirationTime) {
  // Add the root to the schedule.
  // Check if this root is already part of the schedule.
  // 首次渲染 root 没有进入调度
  if (root.nextScheduledRoot === null) {
    // This root is not already scheduled. Add it.
    root.expirationTime = expirationTime;
    // 只有一个 root 的情况
    if (lastScheduledRoot === null) {
      firstScheduledRoot = lastScheduledRoot = root;
      root.nextScheduledRoot = root;
    } else {
      // 多个 root 构建单链表结构
      lastScheduledRoot.nextScheduledRoot = root;
      lastScheduledRoot = root;
      lastScheduledRoot.nextScheduledRoot = firstScheduledRoot;
    }
  } else {
    // This root is already scheduled, but its priority may have increased.
    // 传入的 FiberRoot 已经进入过调度
    const remainingExpirationTime = root.expirationTime;
    if (
      remainingExpirationTime === NoWork ||
      // root的 expirationTime 优先级低
      expirationTime < remainingExpirationTime
    ) {
      // Update the priority.
      // 把当前 root 的优先级设置为当前优先级最高的
      root.expirationTime = expirationTime;
    }
  }
}

batchUpdates 批量更新

setState 调用

demo

import React from 'react'
import { unstable_batchedUpdates as batchedUpdates } from 'react-dom'

export default class BatchedDemo extends React.Component {
  state = {
    number: 0,
  }

  componentDidMount () {
    const btn = document.getElementById('btn');
    // 方法四, 结果输出 1, 2, 3
    btn.addEventListener('click', this.handleClick);
  }

  handleClick = () => {
    // 方法一, 结果输出 0, 0, 0
    // 事件处理函数自带`batchedUpdates`
    this.countNumber()

    // 方法二, 结果输出 1, 2, 3
    // setTimeout中没有`batchedUpdates`
    // setTimeout(() => {
    //   this.countNumber()
    // }, 0)

    // 方法三, 结果输出 0, 0, 0
    // 主动`batchedUpdates`
    // setTimeout(() => {
    //   batchedUpdates(() => this.countNumber())
    // }, 0)

    /**
     * setState为函数式调用,记录在firstUpdate的单链表结构里的payload为 function
     */
  }

  countNumber() {
    const num = this.state.number
    this.setState({
      number: num + 1,
    });
    console.log(this.state.number)
    this.setState(() => {
      return {
        number: num + 2,
      };
    });
    console.log(this.state.number)
    this.setState({
      number: num + 3,
    })
    console.log(this.state.number)
  }

  render() {
    // React 自定义事件调用
    return <button onClick={this.handleClick} >Num: {this.state.number}</button>
    // addEventListener 原生事件调用
    return <button id="btn" >Num: {this.state.number}</button>
  }
}

涉及到更改 isBatchingUpdates 变量的两个主要函数, events模块都引用了这两个函数

function dispatchEvent(topLevelType, nativeEvent) { if (!_enabled) { return; }

var nativeEventTarget = getEventTarget(nativeEvent); var targetInst = getClosestInstanceFromNode(nativeEventTarget); if (targetInst !== null && typeof targetInst.tag === 'number' && !isFiberMounted(targetInst)) { // If we get an event (ex: img onload) before committing that // component's mount, ignore it for now (that is, treat it as if it was an // event on a non-React tree). We might also consider queueing events and // dispatching them after the mount. targetInst = null; }

var bookKeeping = getTopLevelCallbackBookKeeping(topLevelType, nativeEvent, targetInst);

try { // Event queue being processed in the same cycle allows // preventDefault. batchedUpdates(handleTopLevel, bookKeeping); } finally { releaseTopLevelCallbackBookKeeping(bookKeeping); } }

function interactiveUpdates<A, B, R>(fn: (A, B) => R, a: A, b: B): R { if (isBatchingInteractiveUpdates) { return fn(a, b); } // If there are any pending interactive updates, synchronously flush them. // This needs to happen before we read any handlers, because the effect of // the previous event may influence which handlers are called during // this event. if ( !isBatchingUpdates && !isRendering && lowestPriorityPendingInteractiveExpirationTime !== NoWork ) { // Synchronously flush pending interactive updates. performWork(lowestPriorityPendingInteractiveExpirationTime, null); lowestPriorityPendingInteractiveExpirationTime = NoWork; } const previousIsBatchingInteractiveUpdates = isBatchingInteractiveUpdates; const previousIsBatchingUpdates = isBatchingUpdates; isBatchingInteractiveUpdates = true; isBatchingUpdates = true; try { // 这里的 fn 为 dispatchEvent 自定义事件函数 return fn(a, b); } finally { isBatchingInteractiveUpdates = previousIsBatchingInteractiveUpdates; isBatchingUpdates = previousIsBatchingUpdates; if (!isBatchingUpdates && !isRendering) { performSyncWork(); } } }


- batchedUpdates

```js
function batchedUpdates<A, R>(fn: (a: A) => R, a: A): R {
  const previousIsBatchingUpdates = isBatchingUpdates;
  isBatchingUpdates = true;
  try {
    // 这里的 fn 为setState, 每次setState 都会进入 requestWork
    return fn(a);
  } finally {
    isBatchingUpdates = previousIsBatchingUpdates;
    if (!isBatchingUpdates && !isRendering) {
      performSyncWork();
    }
  }
}

方法一: 直接调用setState

方法二: 使用setTimeout, setTimeout(() => (this.setState(xxx))),或者 Promise.then(() => { this.setState({xxx})})

使用setTimeout

方法三: 在setTimeout里主动调用 batchUpdates

主动调用

方法四: 原生事件绑定

原生事件

总结 setState 是同步还是异步

上一篇 下一篇