startTransition

startTransition 是react提供的一个新的api,和useTranstion基本一致。

  1. function App() {
  2. const [inoutVal, setInputValue] = useState(null);
  3. const [searchText, setText] = useState("nullptr");
  4. const listenerForInputChange = e => {
  5. let { value } = e.target;
  6. setInputValue(value);
  7. startTransition(() => {
  8. setText(value);
  9. });
  10. }
  11. return (
  12. <div className="App">
  13. <input value={inoutVal} onChange={listenerForInputChange} />
  14. <section>
  15. <p>{searchText}</p>
  16. </section>
  17. </div>
  18. );
  19. }

栗子中,一个listneer首先是用户输入的更新,这种更新是高优先级的,也就是input输入事件
在进入dispatchSetState获取优先级时取到的是DiscreteEventPriority也就是SyncLane这种是同步的优先级,必须尽快的更新用来响应用户UI。
startTranstion开始时会给全局transtion增加transtion工作对象,执行完成以后回滚

  1. // 删除了dev的运行代码
  2. function startTransition(scope, options) {
  3. // 获取旧的transtion
  4. var prevTransition = ReactCurrentBatchConfig.transition;
  5. ReactCurrentBatchConfig.transition = {};
  6. var currentTransition = ReactCurrentBatchConfig.transition;
  7. try {
  8. scope();
  9. } finally {
  10. // 回滚
  11. ReactCurrentBatchConfig.transition = prevTransition;
  12. }
  13. }
  14. // transition执行且存在更新时
  15. if (isTransition) {
  16. if ( ReactCurrentBatchConfig$3.transition !== null) {
  17. var transition = ReactCurrentBatchConfig$3.transition;
  18. if (!transition._updatedFibers) {
  19. transition._updatedFibers = new Set();
  20. }
  21. transition._updatedFibers.add(fiber);
  22. }
  23. // 获取一个比较低的优先级
  24. // currentEventTransitionLane 在第一次被赋值
  25. // 第二次获取会获取到和上次一样的lane, 会被合并更新
  26. // 但是如果相隔时间太久再次获取到的优先级会比第一次的低,会被批量更新
  27. // 如果优先级比较低而没有被批量更新也可能会被中间打断
  28. if (currentEventTransitionLane === NoLane) {
  29. currentEventTransitionLane = claimNextTransitionLane();
  30. }
  31. return currentEventTransitionLane;
  32. }
  33. function claimNextTransitionLane() {
  34. // nextTransitionLane为全局变量,默认为transition的优先级 64
  35. // transtion也存在优先级比如
  36. // 第一次获取的lane是64 nextTransitionLane 左移 1 = 128
  37. // 第二次获取的lane是128 下次获取的就是256
  38. // lane越大优先级越低,可能最后一个transtin要等好久才能排到队执行任务
  39. var lane = nextTransitionLane;
  40. nextTransitionLane <<= 1;
  41. // transtin lane 最大值为 TransitionLanes = 4194240
  42. // nextTransitonLane 会被赋值为TransitionLane1 初始值亩,。
  43. // 这种情况一般不会发生,如果发生了也就是transtion的任务插队
  44. // 如果一个任务太久没执行,已经过去,还有处理`季饿问题`的方式
  45. if ((nextTransitionLane & TransitionLanes) === 0) {
  46. nextTransitionLane = TransitionLane1;
  47. }
  48. return lane;
  49. }

所以startTrasitin的执行,是一次低优先级的更新发起一次调度,如果中间有其他高优先级任务需要紧急处理,比如及时更新UI,这种就会被打断。

transition正是使用优先级的方式来区分非紧急更新的

useTransition

hook和startTransitin的实现有些区别,应用场景为:
一个巨大的列表里面做实时搜索,或者是计算,结果会存在延迟,需要有些交互所以返回两个值一个pending:bool,


function App() {

  const [inoutVal, setInputValue] = useState(null);
  const [searchText, setText] = useState("nullptr");
  const [pending, start] = useTransition();


  const listenerForInputChange = e => {
    let { value } = e.target;

    start(() => {
      setText(value);
    });

    setInputValue(value);

  }


  return (
    <div className="App">
      <input value={inoutVal} onChange={listenerForInputChange} />


      <section>
        <p>{pending ? "loading..." : searchText}</p>
      </section>

    </div>
  );
}
 ContinuousEventPriority = InputContinuousLane = 4
// options timeoutMs目前并没有启用
function startTransition(setPending, callback, options) {
  // 获取当前的优先级
  var previousPriority = getCurrentUpdatePriority();
  // 设置一个不大于 ContinuousEventPriority = InputContinuousLane = 4
  // 的优先级,下一个setPending会使用到,如果start外已经存在更新任务
  // 本次更新会被合并,如果不存在下次更新如果小于或大于时本次setPending同样会被执行
  setCurrentUpdatePriority(higherEventPriority(previousPriority, ContinuousEventPriority));
  setPending(true);
  // 设置transitin环境
  var prevTransition = ReactCurrentBatchConfig$2.transition;
  ReactCurrentBatchConfig$2.transition = {};
  var currentTransition = ReactCurrentBatchConfig$2.transition;

  try {
    // 第二次更新, 是以transition的优先级去执行
    setPending(false);
    callback();
  } finally {
    setCurrentUpdatePriority(previousPriority);
    ReactCurrentBatchConfig$2.transition = prevTransition;
  }
}

在执行start时,获取的优先级和startTransition一致都是64, 并同时一个不大于用户输入优先级的更新去执行一次更新,后面再以transition的优先级去更新,
transition的更新不同的执行完毕发起的时间和不同的设备的更新段是不一样的,如果时间短会和setPending一起被批量更新,如果时间长仍然能获取到transiton上下文,并且发起一次低优先级的更新

执行时:
image.png
这种在UI中会一闪而过,transition想达到的目的是等到全部更新完毕以后再展示新的ui,而非第一次更新就展示过渡状态。
所以,如果transiton执行时间过长会先展示旧的状态,如果过短,会被合并更新,展示新的状态,而loading的效果只在一瞬间,在执行transition时不会让ui一直处于过渡的状态,看不见页面效果(非常的人性)

useDeferredValue


// 首屏挂载时使用的
function mountDeferredValue(value) {
  var _mountState = mountState(value),
      prevValue = _mountState[0],
      setValue = _mountState[1];

  mountEffect(function () {
    var prevTransition = ReactCurrentBatchConfig$2.transition;
    ReactCurrentBatchConfig$2.transition = {};

    try {
      setValue(value);
    } finally {
      ReactCurrentBatchConfig$2.transition = prevTransition;
    }
  }, [value]);
  return prevValue;
}

// 后续更新使用的
function updateDeferredValue(value) {
  var _updateState = updateState(),
      prevValue = _updateState[0],
      setValue = _updateState[1];

  updateEffect(function () {
    var prevTransition = ReactCurrentBatchConfig$2.transition;
    ReactCurrentBatchConfig$2.transition = {};

    try {
      setValue(value);
    } finally {
      ReactCurrentBatchConfig$2.transition = prevTransition;
    }
  }, [value]);
  return prevValue;
}

defer 延迟的value,也是采用transition实现的。

🕳️?