Published on

优先级转换关系

Authors
  • avatar
    Name
    李丹秋
    Twitter

事件优先级

export function getEventPriority(domEventName: DOMEventName): * {
  switch (domEventName) {
    // Used by SimpleEventPlugin:
    case 'cancel':
    case 'click':
    case 'close':
    case 'contextmenu':
    case 'copy':
    case 'cut':
    case 'auxclick':
    case 'dblclick':
    case 'dragend':
    case 'dragstart':
    case 'drop':
    case 'focusin':
    case 'focusout':
    case 'input':
    case 'invalid':
    case 'keydown':
    case 'keypress':
    case 'keyup':
    case 'mousedown':
    case 'mouseup':
    case 'paste':
    case 'pause':
    case 'play':
    case 'pointercancel':
    case 'pointerdown':
    case 'pointerup':
    case 'ratechange':
    case 'reset':
    case 'resize':
    case 'seeked':
    case 'submit':
    case 'touchcancel':
    case 'touchend':
    case 'touchstart':
    case 'volumechange':
    // Used by polyfills:
    // eslint-disable-next-line no-fallthrough
    case 'change':
    case 'selectionchange':
    case 'textInput':
    case 'compositionstart':
    case 'compositionend':
    case 'compositionupdate':
    // Only enableCreateEventHandleAPI:
    // eslint-disable-next-line no-fallthrough
    case 'beforeblur':
    case 'afterblur':
    // Not used by React but could be by user code:
    // eslint-disable-next-line no-fallthrough
    case 'beforeinput':
    case 'blur':
    case 'fullscreenchange':
    case 'focus':
    case 'hashchange':
    case 'popstate':
    case 'select':
    case 'selectstart':
      return DiscreteEventPriority;
    case 'drag':
    case 'dragenter':
    case 'dragexit':
    case 'dragleave':
    case 'dragover':
    case 'mousemove':
    case 'mouseout':
    case 'mouseover':
    case 'pointermove':
    case 'pointerout':
    case 'pointerover':
    case 'scroll':
    case 'toggle':
    case 'touchmove':
    case 'wheel':
    // Not used by React but could be by user code:
    // eslint-disable-next-line no-fallthrough
    case 'mouseenter':
    case 'mouseleave':
    case 'pointerenter':
    case 'pointerleave':
      return ContinuousEventPriority;
    case 'message': {
      // We might be in the Scheduler callback.
      // Eventually this mechanism will be replaced by a check
      // of the current priority on the native scheduler.
      const schedulerPriority = getCurrentSchedulerPriorityLevel();
      switch (schedulerPriority) {
        case ImmediateSchedulerPriority:
          return DiscreteEventPriority;
        case UserBlockingSchedulerPriority:
          return ContinuousEventPriority;
        case NormalSchedulerPriority:
        case LowSchedulerPriority:
          // TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
          return DefaultEventPriority;
        case IdleSchedulerPriority:
          return IdleEventPriority;
        default:
          return DefaultEventPriority;
      }
    }
    default:
      return DefaultEventPriority;
  }
}

可以看到,一些点击事件,输入事件等都是离散事件优先级(同步优先级),而一些鼠标移动事件,滚动事件等都是连续事件优先级。

switch (schedulerPriority) {
        case ImmediateSchedulerPriority:
          return DiscreteEventPriority;
        case UserBlockingSchedulerPriority:
          return ContinuousEventPriority;
        case NormalSchedulerPriority:
        case LowSchedulerPriority:
          // TODO: Handle LowSchedulerPriority, somehow. Maybe the same lane as hydration.
          return DefaultEventPriority;
        case IdleSchedulerPriority:
          return IdleEventPriority;
        default:
          return DefaultEventPriority;
      }

比较特殊的是message事件,这个事件的优先级是根据当前的调度器优先级来决定的,如果是ImmediateSchedulerPriority,那么就是离散事件优先级,如果是UserBlockingSchedulerPriority,那么就是连续事件优先级,其他的都是默认事件优先级。

事件优先级转换为调度优先级

  var schedulerPriorityLevel;
      switch (lanesToEventPriority(nextLanes)) {
        case DiscreteEventPriority:
          schedulerPriorityLevel = ImmediatePriority;
          break;
        case ContinuousEventPriority:
          schedulerPriorityLevel = UserBlockingPriority;
          break;
        case DefaultEventPriority:
          schedulerPriorityLevel = NormalPriority;
          break;
        case IdleEventPriority:
          schedulerPriorityLevel = IdlePriority;
          break;
        default:
          schedulerPriorityLevel = NormalPriority;
          break;
      }
      // 返回的是一个Task对象
      newCallbackNode = scheduleCallback$1(
        schedulerPriorityLevel,
        performConcurrentWorkOnRoot.bind(null, root)
      );

Lane转换为事件优先级

  function lanesToEventPriority(lanes) {
    var lane = getHighestPriorityLane(lanes);

    if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
      return DiscreteEventPriority;
    }

    if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
      return ContinuousEventPriority;
    }

    if (includesNonIdleWork(lane)) {
      return DefaultEventPriority;
    }

    return IdleEventPriority;
  }

点击事件触发后

当我们点击按钮的时候,实际上并不是直接调用onClick,而是调用的是#root提前经过事件委托的那个函数,其实也就是这个dispatchDiscreteEvent函数,在这个函数中做了一些事情他会收集到这个onClick,然后调用它,并且在中间还做了这样一件事情:

function dispatchDiscreteEvent(
    domEventName,
    eventSystemFlags,
    container,
    nativeEvent
  ) {
    var previousPriority = getCurrentUpdatePriority();
    try {
      setCurrentUpdatePriority(DiscreteEventPriority);
      // 这个函数中包含执行onClick,并且是同步执行
      dispatchEvent(domEventName, eventSystemFlags, container, nativeEvent);
    } finally {
      setCurrentUpdatePriority(previousPriority);
    }
  }

可以看到,在执行onClick的时候,全局的 currentUpdatePriority 已经被设置成了对应的事件优先级,等事件执行完毕,再恢复成原来的优先级,因此在某个事件产生的更新如果去获取更新优先级的话,在不满足前两个判断的情况下,它必然会获取到对应的这个事件优先级。

但是有一些更新并不是由事件产生的,可能是由IO等异步操作产生的,它们的执行可能没有对应的事件,这个时候就走第4个判断,getCurrentEventPriority,我们来看看它的实现:

function getCurrentEventPriority() {
    var currentEvent = window.event;
    if (currentEvent === undefined) {
      return DefaultEventPriority; // 命中这个
    }
    return getEventPriority(currentEvent.type);
}

由IO等异步操作产生的没有事件,因此返回的是 DefaultEventPriority ,它对应的就是DefaultLane,比如

const App = ()=> {
   const [num , setNum] = useState(0)
   useEffect(()=>{
     fetchData().then(res => setNum(res))
   } , [])
   
   return null
}

上面的 setNum() 产生的就是一个无事件的更新,获取的更新优先级就是 DefaultLane

好了讲到这里不知道大家会不会有一个疑问,为什么这里要把事件优先级了再分一下更新优先级呢,已经有了事件优先级不够了吗,反正页面的所有交互都是用事件驱动的,每一种事件规定一种优先级,然后交给后面的流程不就完了吗? 实际上,最核心的原因是因为事件和更新并非一一对应,换句话说,在一个事件中有可能产生多种优先级的更新,你不信的话看看下面的代码: jsx复制代码

const App = () => {
   const [num, setNum] = React.useState(0);
   const [count, setCount] = React.useState(0);

   return (
      <div>
        <h1 id="h1">{num}</h1>
        <button onClick={() => {
          setNum(num + 1) // SyncLane
          React.startTransition(()=>{
            setCount(count + 1) // TransitionLane
          })
        }}>
          {count}
        </button>
      </div>
    );
};

startTransition 可以将某个状态的改变降低优先级至过渡优先级,因此在这一个 onClick 就产生了两种不同优先级的更新,因此当UI发生了某个交互的时候,不能简单的将事件的优先级当作它的更新优先级,而是要再次根据细节判断一下以获取它准确的更新优先级 ,在真实的场景下,有可能存在更加复杂的情况,因此这样的判断显得非常有必要了,现在你知道为什么存在更新优先级了吧?

Lane优先级转换为React事件优先级

export function lanesToEventPriority(lanes: Lanes): EventPriority {
  const lane = getHighestPriorityLane(lanes);
  if (!isHigherEventPriority(DiscreteEventPriority, lane)) {
    return DiscreteEventPriority;
  }
  if (!isHigherEventPriority(ContinuousEventPriority, lane)) {
    return ContinuousEventPriority;
  }
  if (includesNonIdleWork(lane)) {
    return DefaultEventPriority;
  }
  return IdleEventPriority;
}

React事件优先级转换为Scheduler优先级

let schedulerPriorityLevel;
    switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }

调度优先级

当 update 产生后,会根据 update 优先级以及当前未完成的工作进行计算,得出本次任务的渲染优先级。并根据渲染优先级计算出调度优先级。这里需要注意的是,由于调度器实际上是可以脱离 react 存在的模块,因此调度优先级和update/渲染优先级/事件优先级不同并不是 lane 优先级。需要通过以下的逻辑进行转换: js复制代码

switch (lanesToEventPriority(nextLanes)) {
      case DiscreteEventPriority:
        schedulerPriorityLevel = ImmediateSchedulerPriority;
        break;
      case ContinuousEventPriority:
        schedulerPriorityLevel = UserBlockingSchedulerPriority;
        break;
      case DefaultEventPriority:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
      case IdleEventPriority:
        schedulerPriorityLevel = IdleSchedulerPriority;
        break;
      default:
        schedulerPriorityLevel = NormalSchedulerPriority;
        break;
    }

每次一个任务结束,调度器会选出优先级最高的任务进行调度。