• 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>
      )
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29

    这里 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)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    通过上述代码之后,高优先级的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的在链表中的先后关系,下一次重新从头计算状态,这样每次计算的状态都是符合要求的。

  • 相关阅读:
    SpringCloud学习笔记 - 链路监控 - SpringCloud Sleuth
    DispatcherServlet的功能简介说明
    Python基于PHP+MySQL的个人网页设计与实现
    [运维|数据库] MySQL中的存储过程语句,在PostgreSQL中为什么是函数
    7.4缓存
    你知道Golang的模板怎么用吗?带你了解动态文本的生成!
    最长有效括号的问题
    SpringBoot学习小结之Swagger
    Flask 学习-44.Flask-RESTX 请求参数校验reqparse.RequestParser()
    正则表达式(常用最新版)
  • 原文地址:https://blog.csdn.net/qq_41494959/article/details/126440924