前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >React源码解析之scheduleWork(上)

React源码解析之scheduleWork(上)

作者头像
进击的小进进
发布2019-09-17 17:24:15
1.2K0
发布2019-09-17 17:24:15
举报

前言: 你需要知道:浅谈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


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2019-09-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 webchen 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档