专栏首页前端干货和生活感悟React源码解析之scheduleWork(下)

React源码解析之scheduleWork(下)

上篇回顾: React源码解析之scheduleWork(上)

八、scheduleCallbackForRoot() 作用:render()之后,立即执行调度任务

源码:

// Use this function, along with runRootCallback, to ensure that only a single
// callback per root is scheduled. It's still possible to call renderRoot
// directly, but scheduling via this function helps avoid excessive callbacks.
// It works by storing the callback node and expiration time on the root. When a
// new callback comes in, it compares the expiration time to determine if it
// should cancel the previous one. It also relies on commitRoot scheduling a
// callback to render the next level, because that means we don't need a
// separate callback per expiration time.
//同步调用callback
//流程是在root上存取callback和expirationTime,
// 当新的callback调用时,比较更新expirationTime
function scheduleCallbackForRoot(
  root: FiberRoot,
  priorityLevel: ReactPriorityLevel,
  expirationTime: ExpirationTime,
) {
  //获取root的回调过期时间
  const existingCallbackExpirationTime = root.callbackExpirationTime;
  //更新root的回调过期时间
  if (existingCallbackExpirationTime < expirationTime) {
    // New callback has higher priority than the existing one.
    //当新的expirationTime比已存在的callback的expirationTime优先级更高的时候
    const existingCallbackNode = root.callbackNode;
    if (existingCallbackNode !== null) {
      //取消已存在的callback(打断)
      //将已存在的callback节点从链表中移除
      cancelCallback(existingCallbackNode);
    }
    //更新callbackExpirationTime
    root.callbackExpirationTime = expirationTime;
    //如果是同步任务
    if (expirationTime === Sync) {
      // Sync React callbacks are scheduled on a special internal queue
      //在临时队列中同步被调度的callback
      root.callbackNode = scheduleSyncCallback(
        runRootCallback.bind(
          null,
          root,
          renderRoot.bind(null, root, expirationTime),
        ),
      );
    } else {
      let options = null;
      if (expirationTime !== Never) {
        //(Sync-2 - expirationTime) * 10-now()
        let timeout = expirationTimeToMs(expirationTime) - now();
        options = {timeout};
      }
      //callbackNode即经过处理包装的新task
      root.callbackNode = scheduleCallback(
        priorityLevel,
        //bind()的意思是绑定this,xx.bind(y)()这样才算执行
        runRootCallback.bind(
          null,
          root,
          renderRoot.bind(null, root, expirationTime),
        ),
        options,
      );
      if (
        enableUserTimingAPI &&
        expirationTime !== Sync &&
        (executionContext & (RenderContext | CommitContext)) === NoContext
      ) {
        // Scheduled an async callback, and we're not already working. Add an
        // entry to the flamegraph that shows we're waiting for a callback
        // to fire.
        //开始调度callback的标志
        startRequestCallbackTimer();
      }
    }
  }

  // Associate the current interactions with this new root+priority.
  //跟踪这些update,并计数、检测它们是否会报错
  schedulePendingInteractions(root, expirationTime);
}

解析: Fiber机制可以为每一个update任务进行优先级排序,并且可以记录调度到了哪里(schedulePendingInteractions())

同时,还可以中断正在执行的任务,优先执行优先级比当前高的任务(scheduleCallbackForRoot()),之后,还可以继续之前中断的任务,而React16 之前调用setState(),必须等待setStateupdate队列全部调度完,才能进行之后的操作。

一起看下scheduleCallbackForRoot()做了什么: (1)当新的scheduleCallback的优先级更高时,中断当前任务cancelCallback(existingCallbackNode) (2)如果是同步任务,则在临时队列中进行调度 (3)如果是异步任务,则更新调度队列的状态 (4)设置开始调度的时间节点 (5)跟踪调度的任务

具体讲解,请耐心往下看

九、cancelCallback() 作用: 中断正在执行的调度任务

源码:

const {
  unstable_cancelCallback: Scheduler_cancelCallback,
} = Scheduler;

//从链表中移除task节点
function unstable_cancelCallback(task) {
  //获取callbackNode的next节点
  var next = task.next;
  //由于链表是双向循环链表,一旦next是null则证明该节点已不存在于链表中
  if (next === null) {
    // Already cancelled.
    return;
  }
  //自己等于自己,说明链表中就这一个callback节点
  //firstTask/firstDelayedTask应该是类似游标的概念,即正要执行的节点
  if (task === next) {
    //置为null,即删除callback节点
    //重置firstTask/firstDelayedTask
    if (task === firstTask) {
      firstTask = null;
    } else if (task === firstDelayedTask) {
      firstDelayedTask = null;
    }
  } else {
    //将firstTask/firstDelayedTask指向下一节点
    if (task === firstTask) {
      firstTask = next;
    } else if (task === firstDelayedTask) {
      firstDelayedTask = next;
    }
    var previous = task.previous;
    //熟悉的链表操作,删除已存在的callbackNode
    previous.next = next;
    next.previous = previous;
  }

  task.next = task.previous = null;
}

解析: 操作schedule链表,将正要执行的callback“移除”,将游标指向下一个调度任务

十、scheduleSyncCallback() 作用: 如果是同步任务的话,则执行scheduleSyncCallback(),将调度任务入队,并返回入队后的临时队列

源码:

//入队callback,并返回临时的队列
export function scheduleSyncCallback(callback: SchedulerCallback) {
  // Push this callback into an internal queue. We'll flush these either in
  // the next tick, or earlier if something calls `flushSyncCallbackQueue`.
  //在下次调度或调用 刷新同步回调队列 的时候刷新callback队列

  //如果同步队列为空的话,则初始化同步队列,
  //并在下次调度的一开始就刷新队列
  if (syncQueue === null) {
    syncQueue = [callback];
    // Flush the queue in the next tick, at the earliest.
    immediateQueueCallbackNode = Scheduler_scheduleCallback(
      //赋予调度立即执行的高权限
      Scheduler_ImmediatePriority,
      flushSyncCallbackQueueImpl,
    );
  }
  //如果同步队列不为空的话,则将callback入队
  else {
    // Push onto existing queue. Don't need to schedule a callback because
    // we already scheduled one when we created the queue.
    //在入队的时候,不必去调度callback,因为在创建队列的时候就已经调度了
    syncQueue.push(callback);
  }
  //fake我认为是临时队列的意思
  return fakeCallbackNode;
}

解析: (1)当同步队列为空 调用Scheduler_scheduleCallback(),将该callback任务入队,并把该callback包装成newTask,赋给root.callbackNode

Scheduler_scheduleCallback():

const {
  unstable_scheduleCallback: Scheduler_scheduleCallback,
} = Scheduler;

//返回经过包装处理的task
function unstable_scheduleCallback(priorityLevel, callback, options) {
  var currentTime = getCurrentTime();

  var startTime;
  var timeout;

  //更新startTime(默认是现在)和timeout(默认5s)
  if (typeof options === 'object' && options !== null) {
    var delay = options.delay;
    if (typeof delay === 'number' && delay > 0) {
      startTime = currentTime + delay;
    } else {
      startTime = currentTime;
    }
    timeout =
      typeof options.timeout === 'number'
        ? options.timeout
        : timeoutForPriorityLevel(priorityLevel);
  } else {
    // Times out immediately
    // var IMMEDIATE_PRIORITY_TIMEOUT = -1;
    // Eventually times out
    // var USER_BLOCKING_PRIORITY = 250;
    //普通优先级的过期时间是5s
    // var NORMAL_PRIORITY_TIMEOUT = 5000;
    //低优先级的过期时间是10s
    // var LOW_PRIORITY_TIMEOUT = 10000;

    timeout = timeoutForPriorityLevel(priorityLevel);
    startTime = currentTime;
  }
  //过期时间是当前时间+5s,也就是默认是5s后,react进行更新
  var expirationTime = startTime + timeout;
  //封装成新的任务
  var newTask = {
    callback,
    priorityLevel,
    startTime,
    expirationTime,
    next: null,
    previous: null,
  };
  //如果开始调度的时间已经错过了
  if (startTime > currentTime) {
    // This is a delayed task.
    //将延期的callback插入到延期队列中
    insertDelayedTask(newTask, startTime);
    //如果调度队列的头任务没有,并且延迟调度队列的头任务正好是新任务,
    //说明所有任务均延期,并且此时的任务是第一个延期任务
    if (firstTask === null && firstDelayedTask === newTask) {
      // All tasks are delayed, and this is the task with the earliest delay.
      //如果延迟调度开始的flag为true,则取消定时的时间
      if (isHostTimeoutScheduled) {
        // Cancel an existing timeout.
        cancelHostTimeout();
      }
      //否则设为true
      else {
        isHostTimeoutScheduled = true;
      }
      // Schedule a timeout.
      requestHostTimeout(handleTimeout, startTime - currentTime);
    }
  }
  //没有延期的话,则按计划插入task
  else {
    insertScheduledTask(newTask, expirationTime);
    // Schedule a host callback, if needed. If we're already performing work,
    // wait until the next time we yield.
    //更新调度执行的标志
    if (!isHostCallbackScheduled && !isPerformingWork) {
      isHostCallbackScheduled = true;
      requestHostCallback(flushWork);
    }
  }
  //返回经过包装处理的task
  return newTask;
}

当有新的update时,React 默认是 5s 后进行更新(直观地说,就是你更新了开发代码,过 5s,也可以说是最迟过了 5s,网页更新)

Scheduler_scheduleCallback()的作用是: ① 确定当前时间startTime延迟更新时间timeout ② 新建newTask对象(包含callbackexpirationTime) ③ 如果是延迟调度的话,将newTask放入【延迟调度队列】 ④ 如果是正常调度的话,将newTask放入【正常调度队列】 ⑤ 返回包装的newTask

(2)当同步队列不为空 将该callback入队

scheduleSyncCallback()最终返回临时回调节点。


十一、scheduleCallback() 作用: 如果是异步任务的话,则执行scheduleCallback(),对callback进行包装处理,并更新调度队列的状态

源码:

//对callback进行包装处理,并更新调度队列的状态
export function scheduleCallback(
  reactPriorityLevel: ReactPriorityLevel,
  callback: SchedulerCallback,
  options: SchedulerCallbackOptions | void | null,
) {
  //获取调度的优先级
  const priorityLevel = reactPriorityToSchedulerPriority(reactPriorityLevel);
  return Scheduler_scheduleCallback(priorityLevel, callback, options);
}

解析:一样,调用Scheduler_scheduleCallback(),将该callback任务入队,并把该callback包装成newTask,赋给root.callbackNode

tips: func.bind(xx)的意思是func里的this绑定的是xx, 也就是说 是xx调用func方法

注意!func.bind(xx)这仅仅是绑定,而不是调用!

func.bind(xx)()这样才算是xx调用func方法!

至此,scheduleCallbackForRoot()已分析完毕(八到十一)

我们讲到这里了:

export function scheduleUpdateOnFiber(){  
  xxx  
  xxx  
  xxx  
  if (expirationTime === Sync) {      
    if( 第一次render ){      
      xxx
    }else{      
      /*八到十一讲解的内容*/ 
      scheduleCallbackForRoot(root, ImmediatePriority, Sync);    
      //当前没有update时
      if (executionContext === NoContext) {
        //刷新同步任务队列
        flushSyncCallbackQueue();
      }
    }  
  }
}

十二、flushSyncCallbackQueue() 作用: 更新同步任务队列的状态

源码:

//刷新同步任务队列
export function flushSyncCallbackQueue() {
  //如果即时节点存在则中断当前节点任务,从链表中移除task节点
  if (immediateQueueCallbackNode !== null) {
    Scheduler_cancelCallback(immediateQueueCallbackNode);
  }
  //更新同步队列
  flushSyncCallbackQueueImpl();
}

flushSyncCallbackQueueImpl():

//更新同步队列
function flushSyncCallbackQueueImpl() {
  //如果同步队列未更新过并且同步队列不为空
  if (!isFlushingSyncQueue && syncQueue !== null) {
    // Prevent re-entrancy.
    //防止重复执行,相当于一把锁
    isFlushingSyncQueue = true;
    let i = 0;
    try {
      const isSync = true;
      const queue = syncQueue;
      //遍历同步队列,并更新刷新的状态isSync=true
      runWithPriority(ImmediatePriority, () => {
        for (; i < queue.length; i++) {
          let callback = queue[i];
          do {
            callback = callback(isSync);
          } while (callback !== null);
        }
      });
      //遍历结束后置为null
      syncQueue = null;
    } catch (error) {
      // If something throws, leave the remaining callbacks on the queue.
      if (syncQueue !== null) {
        syncQueue = syncQueue.slice(i + 1);
      }
      // Resume flushing in the next tick
      Scheduler_scheduleCallback(
        Scheduler_ImmediatePriority,
        flushSyncCallbackQueue,
      );
      throw error;
    } finally {
      isFlushingSyncQueue = false;
    }
  }
}

解析: 当前调度的任务被中断时,先从链表中“移除”当前节点,并调用flushSyncCallbackQueueImpl ()任务更新同步队列

循环遍历syncQueue,并更新节点的isSync状态(isSync=true)


然后到这里:

export function scheduleUpdateOnFiber(){  
  xxx  
  xxx  
  xxx  
  if (expirationTime === Sync) {      
    if( 第一次render ){      
      xxx
    }else{      
      /*八到十一讲解的内容*/ 
      scheduleCallbackForRoot(root, ImmediatePriority, Sync);    
      if (executionContext === NoContext) {
        //十二讲的内容
        flushSyncCallbackQueue();
      }
    }  
  }
  //如果是异步任务的话,则立即执行调度任务
  //对应if (expirationTime === Sync)
  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);
      }
    }
  }

}

scheduleUpdateOnFiber()的最后这一段没看懂是什么意思,猜测是调度结束之前,更新离散时间。


十三、scheduleWork流程图

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


觉得不错的话,在下方留言都是一种支持 (●'◡'●)ノ

(完)

本文分享自微信公众号 - webchen(webchen1995)

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-22

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • Netty整理 顶

    洗衣机洗衣服(无论阻塞式IO还是非阻塞式IO,都是同步IO模型) 同步阻塞:你把衣服丢到洗衣机洗,然后看着洗衣机洗完,洗好后再去晾衣服(你就干等,啥都不做,阻...

    算法之名
  • React源码解析之FiberRoot

    一、FiberRoot的含义与作用 (1)FiberRoot是整个React应用的起点 (2)FiberRoot包含应用挂载的目标节点(<divid='root...

    进击的小进进
  • 【React】345- React v16.9 新特性[译]

    今天我们发布了 React 16.9。它包含了一些新特性、bug修复以及新的弃用警告,以便与筹备接下来的主要版本。

    pingan8787
  • React入门看这篇就够了

    React 起源于 Facebook 的内部项目,因为该公司对市场上所有 JavaScript MVC 框架,都不满意,就决定自己写一套,用来架设 Instag...

    random_wang
  • SpringBoot之SpringApplication Explain

    Java 配置类或XML上下文配置集合,用于Spring Boot BeanDefinitionLoader读取,并且将配置源解析加载为Spring Bean ...

    Isaac Zhang
  • Reactive(2) 响应式流与制奶厂业务

    在前一篇文章 从Reactive编程到“好莱坞” 中,谈到了响应式的一些概念,讲的有些发散。但仅仅还是停留在概念的层面,对于实战性的东西并没有涉及。所以大家看了...

    美码师
  • [react-router] hashHistory 和 browserHistory 的区别

    react-router提供了三种方式来实现路由,并没有默认的路由,需要在声明路由的时候,显式指定所使用的路由。

    Isaac Zhang
  • React学习(2)-深入浅出JSX

    在Jq,原生javascript时期,在写页面时,往往强调的是内容结构,层叠样式,行为动作要分离,三者之间分工明确,不要耦合在一起

    itclanCoder
  • 基于webpack4搭建的react项目框架

    框架介绍,使用webpac构建的react单页面应用,集成antd。使用webpack-dev-server启动本地服务,加入热更新便于开发调试。使用bundl...

    random_wang
  • 前端工程化实践总结 |

    | 导语 本文主要介绍在前端工程化的一些探索和实践,结合移动端的基础库重构和UI组件库开发这两个项目详细介绍工程化方案 。

    前端迷

扫码关注云+社区

领取腾讯云代金券