React: 如何保证中断之后的状态?

React中异步可中断更新是Concurrent模式最重要的一个特性。那如何保证任务中断之后的状态时正确的呢?

首先看这样一个组件:

function Counter() {
  const [count, dispatch] = useState('start: ')
  console.log('render text', count)
  return (
    <div>
      <button
        onClick={() => {
          // 以不同的优先级调用
          // 3: NormalPriority
          // 1: ImmediatePriority
          unsafe_setCurrentPriorityLevel(3)
          dispatch((state) => state + 'A')

          unsafe_setCurrentPriorityLevel(1)
          dispatch((state) => state + 'B')

          unsafe_setCurrentPriorityLevel(3)
          dispatch((state) => state + 'C')

          unsafe_setCurrentPriorityLevel(1)
          dispatch((state) => state + 'D')
        }}
      >
        123
      </button>
      <div>{count}</div>
    </div>
  )
}

这里 unsafe_setCurrentPriorityLevel 是可以修改调度优先级的函数,即可以手动修改 react-schduler包中的变量 currentPriorityLevel的。如果对调度工作原理不太熟,可以理解为 通过这个函数,dispatch这个动作就有对应的优先级,1表示高优先级,而3优先级较1低,表示普通优先级。

当执行完上述函数之后,Counter这个函数组件所对应的fibermemoizedState中会存在一个如下图的循环链表。(源码中,会先在dispatchAction函数中形成单向循环列表挂在queue.pending,然后在updateReducer拷贝到baseQueue中,这里以状态变更为准,不去考虑这些。)

在这里插入图片描述
上图中每个方块表示一个Update,蓝色表示高优先级,透明表示低优先级baseQueue指向最新的一条更新。

这里需要说明一点的是:在以不同优先级调用dispatch时,会将优先级记录(源码中pendingLanes),然后处理时先取最高优先级(源码中是用lanes去处理优先级,这里简化记为Priority_Flag,伪代码中会用到),所以这里会优先执行蓝色方块的Update

通过baseQueue.next可以拿到链表的第一个Update,下面通过伪代码模拟针对每一个Update做的处理

	newState = 'start: '
	cloneUpdate = null
	// FirstUpdate = A
	// 开始: Update 指向 A
	do {
		// 如果`Update`优先级比
		if (Update.Priority < Priority_Flag) {
			// 克隆时保留优先级
			newUpdate = cloneUpdate(Update)
			// cloneUpdate也是单向循环链表,这里将 newUpdate 拼接上去
			concatNewUpdate(newUpdate)
		}
		// 如果当前 Update 的优先级 大于等于 Hight_Priority,就执行当前 Update
		newState = reduce(newState, action)
		
		// 当存在优先级较低的 Update时, 
		// 也需要 clone 满足优先级的Update
		// 但是这里有一个很重要的点:需要将 优先级 改成最高优先级(红色)
		// 目的是 使用最高优先级之后,后续无论什么优先级,这个Update都会被使用
		if (cloneUpdate !== null) {
			newUpdate = cloneUpdate(Update)
		}		

		// 拼接
		concatNewUpdate(newUpdate)
		
		Update = Update.next // 指向下一个 Update
	} while(Update !== null || Update !== FirstUpdate)

通过上述代码之后,高优先级的Update即上述蓝色方块的Update会被使用:

  • Update-B 通过newState = reduce(newState, action)得到更新之后的结果为 newState = start: B
  • Update-D 通过newState = reduce(newState, action)得到更新之后的结果为 newState = start: BD

之后将cloneQueue再拷贝给BaseQueue

在这里插入图片描述

然后通过commit阶段,第一次打印的结果为 render text start: BD,因为含有未处理完的任务,所以会再次进入调度处理。

因为高优先级已被处理,这时的Priority_Flag为普通优先级。

如果再去执行一次上述的伪代码,那么所有的Update都会被执行,且cloneUpdate一直为null:

  • Update-A: 普通优先级,可以执行,结果为 start: A
  • Update-B: 最高优先级,一定会执行执行,结果为 start: AB
  • Update-C: 普通优先级,可以执行,结果为 start: ABC
  • Update-D: 最高优先级,一定会执行执行,结果为 start: ABCD

所以这一次的结果为 start: ABCD

这里主要是利用链表维护了所有待更新的Update,在每一次调度中执行符合对应优先级的更新,同时保留所有未更新以及更新的Update的在链表中的先后关系,下一次重新从头计算状态,这样每次计算的状态都是符合要求的。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值