react学习系列—— useTransition

134 阅读2分钟

function SlowPost({ data }) {
  let startTime = performance.now();
  while (performance.now() - startTime < 1) {
    // Do nothing for 1 ms per item to emulate extremely slow code
  }

  return (
    <li className="item">
      Post #{data}
    </li>
  );
}

export default function Index(){
  const [ number , setNumber ] = useState(0)
  const [isPending, startTransition] = useTransition();
  const handleConcurrentClick = () => {
    startTransition(() => {
      setNumber((num) => num + 1)
    })
  }
  let items = [];
  for (let i = 0; i < 500; i++) {
    items.push(<SlowPost key={i} data={number} />);
  }
  console.log('----组件渲染----')
  return <div>
    <button onClick={handleConcurrentClick}>触发更新</button>
      <ul className="items">
          {items}
        </ul>
   </div>
}

transition产生了有3次update任务

第一次

dispatchOptimisticSetState(fiber, false, queue, pendingState);

产生一次lane = 2的更新,isPending = true

第二次

var returnValue = callback(); 

回调函数的setState,这里lane = 256

第三次

dispatchSetState(fiber, queue, _entangledResult2);

产生一次lane = 256的更新,isPending = false

这三次update在processRootScheduleInMicrotask中把对应任务装入对应hook

然后进行render,此时lane = 2,进行同步渲染

此时只会进行lane = 2 的update,所以只会将isPending = true

之后commitRoot的时候还会触发一次processRootScheduleInMicrotask,这个时候进行下一轮的任务,lane = 256,进行shedule渲染

var newCallbackNode = scheduleCallback$2(schedulerPriorityLevel, performConcurrentWorkOnRoot.bind(null, root));

等待schedule调度

  var shouldTimeSlice = !includesBlockingLane(root, lanes) && !includesExpiredLane(root, lanes) && (!didTimeout);
  var exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes);

没有过期任务,调度没有超时,进行renderRootConcurrent

renderRootConcurrent会进行workLoopConcurrent,和workLoopSync相比多了能截断

function workLoopConcurrent() {
  // Perform work until Scheduler asks us to yield
  while (workInProgress !== null && !shouldYield()) {
    // $FlowFixMe[incompatible-call] found when upgrading Flow
    performUnitOfWork(workInProgress);
  }
}

shouldYield的逻辑是超出了5ms,返回true

如果截断后workInProgress !==null, 则说明render任务没有结束

后面又会注册processRootScheduleInMicrotask

    ensureRootIsScheduled(root); // 注册processRootScheduleInMicrotask
     return getContinuationForRoot(root, originalCallbackNode);
function getContinuationForRoot(root, originalCallbackNode) {
  // This is called at the end of `performConcurrentWorkOnRoot` to determine
  // if we need to schedule a continuation task.
  //
  // Usually `scheduleTaskForRootDuringMicrotask` only runs inside a microtask;
  // however, since most of the logic for determining if we need a continuation
  // versus a new task is the same, we cheat a bit and call it here. This is
  // only safe to do because we know we're at the end of the browser task.
  // So although it's not an actual microtask, it might as well be.
  scheduleTaskForRootDuringMicrotask(root, now$1());

  if (root.callbackNode === originalCallbackNode) {
    // The task node scheduled for this root is the same one that's
    // currently executed. Need to return a continuation.
    return performConcurrentWorkOnRoot.bind(null, root);
  }sheduleTask还没有执行完

  return null;
}

同步getContinuationForRoot会进行一次shedule挂载,但是之前的sheduleTask还没有执行完,新的sheduleTask优先级和之前一样,所以保留之前的sheduleTask

之后返回performConcurrentWorkOnRoot给schedule,由于sheduleTask的执行结果还是个函数,shedule会在下一个宏任务继续执行这个函数,直到它执行完成,然后再去任务队列里面执行下一个任务,这也是concurrent模式下render fiber不会被其他任务打断的原因

如果是超时状态,那么shouldTimeSlice = false,会进行renderRootSync

schedule的优先级为3对应着5s,所以在这5s内的渲染都是以5ms为间隔划分,超时之后变为同步渲染

截屏2024-03-28 13.11.53.png

这张是触发isPending的同步渲染,可以看出task已经超时很久了

截屏2024-03-28 13.13.15.png

紧接着的是concurrennt渲染,每个task都超5ms一点点,也就是这个fiber到下一个fiber的时候会判断如果超了5ms就会推迟到下一个task