React最难理解的部分就是“调度算法”,不仅抽象复杂,而且还要重构一次。可以说只有React团队才能完全理解这个算法。在这种情况下,本文试图从React团队成员的角度来谈谈“调度算法”。什么是调度算法?React在v16之前面临的主要性能问题是:当组件树非常大时,更新状态可能会导致页面卡顿。根本原因在于更新过程是“同步且不可中断”的。为了解决这个问题,React提出了Fiber架构,意在“让更新过程异步且可中断”。最终的交互过程如下:不同的交互产生不同优先级的更新(比如onClick回调中的更新优先级最高,useEffect回调中触发的更新优先级平均)。“调度算法”从众多更新中选择一个优先级作为本次渲染的优先级,即渲染步骤2中选择的优先级的组件树。在渲染过程中,如果再次触发交互过程,则更高的优先级为在步骤2中选择,之前的渲染被打断,从新的开始前端训练。优先重启渲染。本文要讲的是第2步中的“调度算法”。expirationTime调度算法“调度算法”中需要解决的最基本的问题是:如何选择其中一个更新的优先级作为这个渲染的优先级?最早的算法称为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。过期时间;意味着u1具有更高的优先级。expirationTime算法的原理简单易懂:每次都选择所有更新中“优先级最高”的那个。如何表示“batch”此外,还有一个问题需要解决:如何表示“batch”?什么是“批次”?考虑以下示例://定义状态numconst[num,updateNum]=useState(0);//...某处修改num//修改方式1updateNum(3);//修改方式2updateNum(num=>num+1);两种“修改状态的方法”都会创建更新,不同的是:第一种方法不需要考虑更新前的状态,直接修改状态num为3第二种方法需要根据“更新”Previousstate”来计算新状态由于第二种方法的存在,更新之间可能存在连续性。因此,“调度算法”计算出一个优先级后,实际参与计算“当前状态值”的是什么时候组件渲染的是:“计算出的优先级的对应更新”+“与该优先级相关的其他优先级的对应更新”这些相互关联的、顺序的更新被称为一个“批次”。expirationTime算法计算“批次”的方式也很简单,crude:优先级大于某个值(priorityOfBatch)的更新会被归为同一个batchconstisUpdateIncludedInBatch=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);},[]);return(<>
