React源码解析之scheduleWork(上)

  • 2019 年 10 月 4 日
  • 筆記

前言: 你需要知道:浅谈React 16中的Fiber机制(https://tech.youzan.com/react-fiber/)、React源码解析之RootFiberReact源码解析之FiberRoot

在之前的文章中讲到,React更新的方式有三种: (1)ReactDOM.render() || hydrate(ReactDOMServer渲染) (2)setState() (3)forceUpdate()

createUpdate后就进入scheduleWork流程,接下来我们就正式进入调度流程

一、scheduleUpdateOnFiber() 作用: 调度update任务

提示: scheduleWorkscheduleUpdateOnFiber

export const scheduleWork = scheduleUpdateOnFiber;  

源码:

//scheduleWork  export function scheduleUpdateOnFiber(    fiber: Fiber,    expirationTime: ExpirationTime,  ) {    //判断是否是无限循环update    checkForNestedUpdates();    //测试环境用的,不看    warnAboutInvalidUpdatesOnClassComponentsInDEV(fiber);    //找到rootFiber并遍历更新子节点的expirationTime    const root = markUpdateTimeFromFiberToRoot(fiber, expirationTime);    if (root === null) {      warnAboutUpdateOnUnmountedFiberInDEV(fiber);      return;    }    //NoWork表示无更新操作    root.pingTime = NoWork;    //判断是否有高优先级任务打断当前正在执行的任务    checkForInterruption(fiber, expirationTime);    //报告调度更新,测试环境用的,可不看    recordScheduleUpdate();      // TODO: computeExpirationForFiber also reads the priority. Pass the    // priority as an argument to that function and this one.    const priorityLevel = getCurrentPriorityLevel();    //1073741823    //如果expirationTime等于最大整型值的话    //如果是同步任务的过期时间的话    if (expirationTime === Sync) {      //如果还未渲染,update是未分批次的,      //也就是第一次渲染前      if (        // Check if we're inside unbatchedUpdates        (executionContext & LegacyUnbatchedContext) !== NoContext &&        // Check if we're not already rendering        (executionContext & (RenderContext | CommitContext)) === NoContext      ) {        // Register pending interactions on the root to avoid losing traced interaction data.        //跟踪这些update,并计数、检测它们是否会报错        schedulePendingInteractions(root, expirationTime);          // This is a legacy edge case. The initial mount of a ReactDOM.render-ed        // root inside of batchedUpdates should be synchronous, but layout updates        // should be deferred until the end of the batch.        //批量更新时,render是要保持同步的,但布局的更新要延迟到批量更新的末尾才执行          //初始化root        //调用workLoop进行循环单元更新        let callback = renderRoot(root, Sync, true);        while (callback !== null) {          callback = callback(true);        }      }      //render后      else {        //立即执行调度任务        scheduleCallbackForRoot(root, ImmediatePriority, Sync);        //当前没有update时        if (executionContext === NoContext) {          // Flush the synchronous work now, wnless we're already working or inside          // a batch. This is intentionally inside scheduleUpdateOnFiber instead of          // scheduleCallbackForFiber to preserve the ability to schedule a callback          // without immediately flushing it. We only do this for user-initated          // updates, to preserve historical behavior of sync mode.          //刷新同步任务队列          flushSyncCallbackQueue();        }      }    }    //如果是异步任务的话,则立即执行调度任务    else {      scheduleCallbackForRoot(root, priorityLevel, expirationTime);    }      if (      (executionContext & DiscreteEventContext) !== NoContext &&      // Only updates at user-blocking priority or greater are considered      // discrete, even inside a discrete event.      // 只有在用户阻止优先级或更高优先级的更新才被视为离散,即使在离散事件中也是如此      (priorityLevel === UserBlockingPriority ||        priorityLevel === ImmediatePriority)    ) {      // This is the result of a discrete event. Track the lowest priority      // discrete update per root so we can flush them early, if needed.      //这是离散事件的结果。 跟踪每个根的最低优先级离散更新,以便我们可以在需要时尽早清除它们。      //如果rootsWithPendingDiscreteUpdates为null,则初始化它      if (rootsWithPendingDiscreteUpdates === null) {        //key是root,value是expirationTime        rootsWithPendingDiscreteUpdates = new Map([[root, expirationTime]]);      } else {        //获取最新的DiscreteTime        const lastDiscreteTime = rootsWithPendingDiscreteUpdates.get(root);        //更新DiscreteTime        if (lastDiscreteTime === undefined || lastDiscreteTime > expirationTime) {          rootsWithPendingDiscreteUpdates.set(root, expirationTime);        }      }    }  }  

解析: 有点长,我们慢慢看

二、checkForNestedUpdates() 作用: 判断是否是无限循环的update

源码:

// Use these to prevent an infinite loop of nested updates  const NESTED_UPDATE_LIMIT = 50;  let nestedUpdateCount: number = 0;  //防止无限循环地嵌套更新  function checkForNestedUpdates() {    if (nestedUpdateCount > NESTED_UPDATE_LIMIT) {      nestedUpdateCount = 0;      rootWithNestedUpdates = null;      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.',      );    }  }  

解析: 超过 50层嵌套update,就终止进行调度,并报出error

常见的造成死循环的为两种情况: ① 在render()中无条件调用setState() 注意: 有条件调用setState()的话,是可以放在render()中的

render(){    if(xxx){    this.setState({ yyy })  }  }  

② 如下图,在shouldComponentUpdate()componentWillUpdate()中调用setState()

setState死循环

三、markUpdateTimeFromFiberToRoot() 作用: 找到rootFiber并遍历更新子节点的expirationTime

源码:

//目标fiber会向上寻找rootFiber对象,在寻找的过程中会进行一些操作  function markUpdateTimeFromFiberToRoot(fiber, expirationTime) {    // Update the source fiber's expiration time    //如果fiber对象的过期时间小于 expirationTime,则更新fiber对象的过期时间      //也就是说,当前fiber的优先级是小于expirationTime的优先级的,现在要调高fiber的优先级    if (fiber.expirationTime < expirationTime) {      fiber.expirationTime = expirationTime;    }    //在enqueueUpdate()中有讲到,与fiber.current是映射关系    let alternate = fiber.alternate;    //同上    if (alternate !== null && alternate.expirationTime < expirationTime) {      alternate.expirationTime = expirationTime;    }    // Walk the parent path to the root and update the child expiration time.    //向上遍历父节点,直到root节点,在遍历的过程中更新子节点的expirationTime      //fiber的父节点    let node = fiber.return;    let root = null;    //node=null,表示是没有父节点了,也就是到达了RootFiber,即最大父节点    //HostRoot即树的顶端节点root    if (node === null && fiber.tag === HostRoot) {      //RootFiber的stateNode就是FiberRoot      root = fiber.stateNode;    }    //没有到达FiberRoot的话,则进行循环    else {      while (node !== null) {        alternate = node.alternate;        //如果父节点的所有子节点中优先级最高的更新时间仍小于expirationTime的话        //则提高优先级        if (node.childExpirationTime < expirationTime) {          //重新赋值          node.childExpirationTime = expirationTime;          //alternate是相对于fiber的另一个对象,也要进行更新          if (            alternate !== null &&            alternate.childExpirationTime < expirationTime          ) {            alternate.childExpirationTime = expirationTime;          }        }        //别看差了是对应(node.childExpirationTime < expirationTime)的if        else if (          alternate !== null &&          alternate.childExpirationTime < expirationTime        ) {          alternate.childExpirationTime = expirationTime;        }        //如果找到顶端rootFiber,结束循环        if (node.return === null && node.tag === HostRoot) {          root = node.stateNode;          break;        }        node = node.return;      }    }    //更新该rootFiber的最旧、最新的挂起时间    if (root !== null) {      // Update the first and last pending expiration times in this root      const firstPendingTime = root.firstPendingTime;      if (expirationTime > firstPendingTime) {        root.firstPendingTime = expirationTime;      }      const lastPendingTime = root.lastPendingTime;      if (lastPendingTime === NoWork || expirationTime < lastPendingTime) {        root.lastPendingTime = expirationTime;      }    }      return root;  }  

解析: markUpdateTimeFromFiberToRoot()主要做了以下事情 (1)更新fiber对象的expirationTime (2)根据fiber.return向上遍历寻找RootFiber(fiber的顶层对象) (3)在向上遍历的过程中,更新父对象fiber.return子节点的childExpirationTime

关于RootFiber,请参考:React源码解析之RootFiber

(4)找到RootFiber后,根据RootFiber.stateNode=FiberRoot的关系,找到FiberRoot

(5)更新该rootFiber的最旧、最新的挂起时间 (6)返回RootFiber

四、checkForInterruption() 作用: 判断是否有高优先级任务打断当前正在执行的任务

源码:

//判断是否有高优先级任务打断当前正在执行的任务  function checkForInterruption(    fiberThatReceivedUpdate: Fiber,    updateExpirationTime: ExpirationTime,  ) {    //如果任务正在执行,并且异步任务已经执行到一半了,    //但是现在需要把执行权交给浏览器,去执行优先级更高的任务    if (      enableUserTimingAPI &&      workInProgressRoot !== null &&      updateExpirationTime > renderExpirationTime    ) {      //打断当前任务,优先执行新的update      interruptedBy = fiberThatReceivedUpdate;    }  }  

解析: 如果当前fiber的优先级更高,需要打断当前执行的任务,立即执行该fiber上的update,则更新interruptedBy

五、getCurrentPriorityLevel() 作用: 获取当前调度任务的优先级

源码:

// Except for NoPriority, these correspond to Scheduler priorities. We use  // ascending numbers so we can compare them like numbers. They start at 90 to  // avoid clashing with Scheduler's priorities.  //除了90,用数字是因为这样做,方便比较  //从90开始的原因是防止和Scheduler的优先级冲突  export const ImmediatePriority: ReactPriorityLevel = 99;  export const UserBlockingPriority: ReactPriorityLevel = 98;  export const NormalPriority: ReactPriorityLevel = 97;  export const LowPriority: ReactPriorityLevel = 96;  export const IdlePriority: ReactPriorityLevel = 95;  // NoPriority is the absence of priority. Also React-only.  export const NoPriority: ReactPriorityLevel = 90;    //获取当前调度任务的优先级  export function getCurrentPriorityLevel(): ReactPriorityLevel {    switch (Scheduler_getCurrentPriorityLevel()) {        //99      case Scheduler_ImmediatePriority:        return ImmediatePriority;        //98      case Scheduler_UserBlockingPriority:        return UserBlockingPriority;        //97      case Scheduler_NormalPriority:        return NormalPriority;        //96      case Scheduler_LowPriority:        return LowPriority;        //95      case Scheduler_IdlePriority:        return IdlePriority;      default:        invariant(false, 'Unknown priority level.');    }  }  

Scheduler_getCurrentPriorityLevel():

const {    unstable_getCurrentPriorityLevel: Scheduler_getCurrentPriorityLevel,  } = Scheduler;    function unstable_getCurrentPriorityLevel() {    return currentPriorityLevel;  }  //当前调度优先级默认为 NormalPriority  var currentPriorityLevel = NormalPriority;  

解析: 记住scheduler优先级是90往上,并且默认是NormalPriority(97)


如果是同步任务,并且是初次render()的话,会先执行schedulePendingInteractions()


六、schedulePendingInteractions() 作用: 跟踪需要同步执行的update们,并计数、检测它们是否会报错

源码: schedulePendingInteractions():

//跟踪这些update,并计数、检测它们是否会报错  function schedulePendingInteractions(root, expirationTime) {    // This is called when work is scheduled on a root.    // It associates the current interactions with the newly-scheduled expiration.    // They will be restored when that expiration is later committed.    //当调度开始时就执行,每调度一个update,就更新跟踪栈    if (!enableSchedulerTracing) {      return;    }    //调度的"交互"    scheduleInteractions(root, expirationTime, __interactionsRef.current);  }  

__interactionsRef:

// Set of currently traced interactions.  // Interactions "stack"–  // Meaning that newly traced interactions are appended to the previously active set.  // When an interaction goes out of scope, the previous set (if any) is restored.     //设置当前跟踪的interactions,也是interactions的栈  //它是一个集合  let interactionsRef: InteractionsRef = (null: any);  

scheduleInteractions():

//与schedule的交互  function scheduleInteractions(root, expirationTime, interactions) {    if (!enableSchedulerTracing) {      return;    }    //当interactions存在时    if (interactions.size > 0) {      //获取FiberRoot的pendingInteractionMap属性      const pendingInteractionMap = root.pendingInteractionMap;      //获取pendingInteractions的expirationTime      const pendingInteractions = pendingInteractionMap.get(expirationTime);      //如果pendingInteractions不为空的话      if (pendingInteractions != null) {        //遍历并更新还未调度的同步任务的数量        interactions.forEach(interaction => {          if (!pendingInteractions.has(interaction)) {            // Update the pending async work count for previously unscheduled interaction.            interaction.__count++;          }            pendingInteractions.add(interaction);        });      }      //否则初始化pendingInteractionMap      //并统计当前调度中同步任务的数量      else {        pendingInteractionMap.set(expirationTime, new Set(interactions));          // Update the pending async work count for the current interactions.        interactions.forEach(interaction => {          interaction.__count++;        });      }      //计算并得出线程的id      const subscriber = __subscriberRef.current;      if (subscriber !== null) {        //这个暂时不看了        const threadID = computeThreadID(root, expirationTime);        //检测这些任务是否会报错        subscriber.onWorkScheduled(interactions, threadID);      }    }  }  

subscriber.onWorkScheduled():

//利用线程去检测同步的update,判断它们是否会报错  function onWorkScheduled(    interactions: Set<Interaction>,    threadID: number,  ): void {    let didCatchError = false;    let caughtError = null;    //遍历去检测    subscribers.forEach(subscriber => {      try {        subscriber.onWorkScheduled(interactions, threadID);      } catch (error) {        if (!didCatchError) {          didCatchError = true;          caughtError = error;        }      }    });      if (didCatchError) {      throw caughtError;    }  }  

解析: 利用FiberRootpendingInteractionMap属性和不同的expirationTime,获取每次schedule所需的update任务的集合,记录它们的数量,并检测这些任务是否会出错。

七、renderRoot() 作用: 初始化root,并调用workLoop进行循环单元更新

这个我们放在后面再讲。


后言: 本来是写到十一了,但发现还有一层套一层的function,全部放在一篇文章里的话,太长了,容易消化不了,所以我们先在这里打住:

export function scheduleUpdateOnFiber(){    xxx    xxx    xxx    if (expirationTime === Sync) {      if( 第一次render ){        //跟踪这些update,并计数、检测它们是否会报错        schedulePendingInteractions(root, expirationTime);        //初始化root,调用workLoop进行循环单元更新        let callback = renderRoot(root, Sync, true);        }else{        下篇要讲的内容。。。。      }    }    }  

scheduleWork 结束后,我会像往常一样,制作一张流程图帮助大家梳理思路!

源码请参考GitHub: https://github.com/AttackXiaoJinJin/reactExplain/blob/master/react16.8.6/packages/react-reconciler/src/ReactFiberWorkLoop.js