suspense

React 中的suspendedTime用来记录的是还没有被提交但是已经被更新的任务,在这里首先我要讲解一个概念,那就是finishedWork。一开始我以为finishedWork存储的是workInProgress,也就是current的引用,那存的不就是自己么,但是后来我弄清楚了。试想我们从renderRoot开始做的事情都是在workInProgress上操作的,这个对象通过调用

nextUnitOfWork = createWorkInProgress(
  nextRoot.current,
  null,
  nextRenderExpirationTime,
)

export function createWorkInProgress(
  current: Fiber,
  pendingProps: any,
  expirationTime: ExpirationTime,
): Fiber {
  let workInProgress = current.alternate
  if (workInProgress === null) {
    workInProgress = createFiber(
      current.tag,
      pendingProps,
      current.key,
      current.mode,
    )
    workInProgress.elementType = current.elementType
    workInProgress.type = current.type
    workInProgress.stateNode = current.stateNode

    workInProgress.alternate = current
    current.alternate = workInProgress
  } else {
    workInProgress.pendingProps = pendingProps

    workInProgress.effectTag = NoEffect

    // The effect list is no longer valid.
    workInProgress.nextEffect = null
    workInProgress.firstEffect = null
    workInProgress.lastEffect = null
  }

  workInProgress.childExpirationTime = current.childExpirationTime
  workInProgress.expirationTime = current.expirationTime

  workInProgress.child = current.child
  workInProgress.memoizedProps = current.memoizedProps
  workInProgress.memoizedState = current.memoizedState
  workInProgress.updateQueue = current.updateQueue
  workInProgress.firstContextDependency = current.firstContextDependency

  workInProgress.sibling = current.sibling
  workInProgress.index = current.index
  workInProgress.ref = current.ref

  return workInProgress
}

注意这里返回的是一个新的对象,在更新的过程中我们对于需要更新的节点都会创建这么一个对象,并对其拷贝属性。对于原始类型的数据那么是值拷贝,对于引用类型是否是引用拷贝呢?答案是:对于在更新过程中需要改变的对象,会创建一个新的对象,这也是创建workInProgress的原因,这里尤其需要注意的是updateQueue,在调用processUpdateQueue的时候,我们需要调用一个方法

function ensureWorkInProgressQueueIsAClone<State>(
  workInProgress: Fiber,
  queue: UpdateQueue<State>,
): UpdateQueue<State> {
  const current = workInProgress.alternate
  if (current !== null) {
    if (queue === current.updateQueue) {
      queue = workInProgress.updateQueue = cloneUpdateQueue(queue)
    }
  }
  return queue
}

也就是说updateQueue也是一个新的对象,所以在更新过程中改变的Update的顺序关系,并不会影响到current上的顺序,需要注意的是新创建的update调用enqueueUpdate会往currentworkInProgress都插入。

说了这么多我到底要说什么呢?答案就是被挂起的work其实就是不把workInProgress的整棵树进入commit阶段的操作,挂起是专指commit的,只有commit阶段可以被挂起。而被挂起的提交,除了timeout的情况,我们都可以认为这次更新被抛弃了。

onTimeout

root.timeoutHandler用来记录子树中需要timeout执行的任务,这类任务被挂起的原因一般是因为抛出了promise,并且自上次提交经过了maxDruation时间的任务,nextLatestAbsoluteTimeoutMs就是用来记录子树中优先级最高的需要提交的被挂起的任务的。被挂起代表了任务已经更新完成,但是因为一些原因不需要立马提交,比如:

  • 有错误出现,并且有低优先级的更新,那么可以跟低优先级的更新一起被提交
  • 有错误但是没有低优先级的任务,发起一次同步的更新,再次执行被挂起的优先级的任务
  • Suspense组件接收到异常的情况,根据情况挂起一些时间,最长为优先级最高的任务的过期时间,如果超时会执行flushRoot强制提交

需要注意的是,被挂起的任务只有Suspense的情况是设置了timeout的,但是不代表他一定要等到timeout才会被提交,以下情况都会提早提交:

  • 有任务的expirationTime小于timeout的时长
  • timeout期间同一子树产生了新的更新,连同Suspense组件一起更新了
  • Promise状态改变,调度了新的更新

设置了timeoutHandler之后,在下一次开始更新之前会清除handler,如果新的更新改变了isExpire或者nextLatestAbsoluteTimeoutMs,那么会导致timeoutHandler变化,甚至不需要timeout

retry

retry只是简单地发起一次新的调度,所以并不关心新的渲染是否还需要这个promise返回的数据。

retry的时候,如果他处于ConcurrentMode,那么只需要重新设置节点的expirationTimeretryTime即可

这里注意,因为同步模式下Suspense组件直接渲染null,不会像异步模式下一样操作,所以同步模式下他已经没有更新了,所以需要创建新的更新

error

注意重新发起一次同步请求的条件是比较苛刻的,就是要符合!root.didError!isExpire,也就是同步任务是不会进入这个分支的,只会直接提交。

results matching ""

    No results matching ""

    Jokcy的二维码

    扫码添加Jokcy,更多更新更优质的前端学习内容不断更新中,期待与你一起成长!

    Jokcy的二维码