大家好,我是Kason。React内部最难理解的就是调度算法,不仅抽象复杂,而且还要重构一次。可以说只有React团队才能完全理解这个算法。在这种情况下,本文试图从React团队成员的角度来谈谈调度算法。欢迎加入人类优质前端框架群。什么是调度算法?React在v16之前面临的主要性能问题是:当组件树非常大时,更新状态可能会导致页面卡顿。根本原因是:更新过程是同步的,不可中断的。为了解决这个问题,React提出了Fiber架构,旨在让更新过程异步和可中断。最终的交互过程是这样的:不同的交互产生不同优先级的更新(比如onClick回调中的更新优先级最高,useEffect回调中触发的更新优先级平均)。调度算法从众多更新中选择一个优先级作为本次。render的优先级以第2步选择的优先级渲染组件树,在渲染过程中,如果再次触发交互过程,在第2步选择了更高的优先级,则中断之前的渲染,并以新的优先级重新开始渲染。本文要讲的是第2步中的调度算法。expirationTimeSchedulingAlgorithm调度算法需要解决的最基本的问题是:如何选择其中一个update的优先级作为本次render的优先级?最早的算法称为expirationTime算法。具体更新优先级与当前触发交互的时间和优先级对应的延迟时间有关://MAX_SIGNED_31_BIT_INT最大为31位Intergerupdate.expirationTime=MAX_SIGNED_31_BIT_INT-(currentTime+updatePriority);例如高优先级更新u1,低优先级更新u2的updatePriority分别为0和200,则MAX_SIGNED_31_BIT_INT-(currentTime+0)>MAX_SIGNED_31_BIT_INT-(currentTime+200)//即u1.expirationTime>u2.expirationTime;意味着u1具有更高的优先级。expirationTime算法的原理简单易懂:每次都选择所有更新中优先级最高的。如何表示“batch”另外,还有一个问题需要解决:如何表示batch?什么是批次?考虑以下示例://定义状态numconst[num,updateNum]=useState(0);//...某处修改num//修改方式1updateNum(3);//修改方式2updateNum(num=>num+1);两种修改状态的方法都会创建一个更新,不同的是:第一种方法不需要考虑更新前的状态,直接修改状态num为3第二种方法需要根据更新前的状态计算新状态由于第二种方式,更新之间可能存在连续性。因此,调度算法计算出一个优先级后,真正参与组件渲染时当前状态值计算的是:计算出的优先级对应更新+与该优先级相关的其他优先级对应更新。这些是相互关联的,连续更新称为批处理。expirationTime算法计算批次的方式也很简单粗暴:优先级大于某个值(priorityOfBatch)的更新会被归为同一批次。constisUpdateIncludedInBatch=priorityOfUpdate>=priorityOfBatch;expirationTime算法确保呈现是异步可中断的,并且始终首先处理具有最高优先级的更新。此功能在此期间称为异步模式。IO密集场景下的AsyncMode可以解决以下问题:组件树逻辑复杂导致更新时卡顿(因为组件渲染变得可中断),重要交互响应更快(因为不同交互产生不同优先级的更新).这些问题统称为CPU密集型问题。在前端,还有一类问题也会影响体验,那就是请求数据带来的等待。此类问题称为IO绑定问题。为了解决IO密集型问题,React提出了Suspense。考虑以下代码:constApp=()=>{const[count,setCount]=useState(0);useEffect(()=>{constt=setInterval(()=>{setCount(count=>count+1);},1000);return()=>clearInterval(t);},[]);返回(<>
