React使用concurrent模式,渲染阶段超出时间片,高优先级任务插队,上一个任务上fiber上的lanes会怎么解决

React使用concurrent模式,产生更新需要时(如setState),执行markUpdateLaneFromFiberToRoot函数,执行

sourceFiber.lanes = mergeLanes(sourceFiber.lanes, lane);

为fiber.lanes赋值,表示该组件有更新,在render阶段执行beginwork时,其中需要拿fiber.lanes和此次任务更新的优先级做比较,判断该组件的优先级够不够,

else if (!includesSomeLane(renderLanes, updateLanes)) { 
  didReceiveUpdate = false;
  ...
}

但是之后会开始生成组件子节点结点(类组件render函数返回的jsx或者函数组件返回值)前会执行

workInProgress.lanes = NoLanes;

如果有一个任务需要渲染100个组件,执行时间很长,当beginwork到第50个组件fiber时时间片用完且有一个高优先级任务产生对其进行插队,之前的任务被取消,但是之前执行完beginwork的前50个组件fiber的lanes被清空了,如果这个高优先级任务执行完,再重新执行100个组件fiber的渲染,前50个fiber的lanes不是就找不到了

阅读 1.9k
1 个回答

问题分析

总体上来说,React会保存两个版本的lanes,分别存储在workInProgress fiber树中,和current fiber树中

  • current fiber树中的lanes存储着当前未完成任务对应的lanes
  • workInProgress fiber树中存储着完成当前render中的任务后,剩下那些任务对应的lanes

一次render总是对应着一颗workInProgress树的构建,存储在它里面的lanes就是这个时候计算出来的,原因也很简单,当每一轮render进行时,渲染它所对应的renderLane是可以确定的,所以我们我可知道这轮render会结束那些任务(每一个任务都是和一个lane绑定的),那么刨除掉这些任务对应的lanes,剩下的lanes就是上面workInProgress fiber树中定义的那些lanes。

从上面的分析中你应该已经知道问题的答案了,既然workInProgress树中存储的lanes要下一次render才用得到,也就意味着,在当前render还没结束之前我们可以抛弃掉这些lanes而不会造成什么影响,大不了待会我在重新构建workInProgress树时再算一次就行

实际例子

考虑下面的伪代码,这个组件会在mount后会调用fetchData获取远程数据,获取数据后调用setList设置list,同时还有一个输入框,当输入框内容变更时会调用setName设置name

function App() {
  const [list, setList] = useState([])
  const [name, setName] = useState('')
  useEffect(() => {
    fetchData(..., (list) => {
      setList(list)
    })
  }, [])
  return (
    <div className="App">
      {name}
      <input onChange={v => setName(v.target.value)}/>
      <List data={list}/>
    </div>
  );
}

存在着这么一种情况,当远程数据返回后,并且返回数据量很大渲染可能会很耗时,此时当List组件渲染到一半时,用户用在输入框中输入值,此时React,就会中止List的渲染而去渲染用户的输入值name(这个也很好理解对于接口的返回值对页面的影响,让用户多等个几十ms甚至更长的时间这是完全可以接受的,但是要是在输入框输入一个值,界面要等个几十ms才会发生变更,用户反而会对这种情况更敏感,第一印象就是你的应用很卡,不跟手),接下来我们就来分析一下这段时间内React做了那些事。

  1. Mount后App组件的状态:

    //current
    states = {list: [], name: ''}
    updateQueue = []
  2. 接口返回后App组件的状态

    //current
    states = {list: [], name: ''}
    updateQueue = {
      list: [{lane: 0b0010, payload: remoteData /* 接口返回的数据 */ }],
      name: [],
    }
  3. React调度了updateQueue中优先级最高的更新,开始一轮render过程,此时由于更新列表中只有一个更新 renderLane就是setList发起的任务的任务的lane,也就是0b0010

    //current
    states = {list: [], name: ''}
    updateQueue = {
      list: [{lane: 0b0010, payload: remoteData /* 接口返回的数据 */ }],
      name: [],
    }
    
    //workInProgress
    states = {list: remoteData, name: ''}
    updateQueue = {
      list: [],
      name: [],
    }
  4. 用户在输入框中输入值,中止了第三步的更新上面workInProgress中计算到一半的lanes和states会被丢弃

    //current
    states = {list: [], name: ''}
    updateQueue = {
      list: [{lane: 0b0010, payload: remoteData /* 接口返回的数据 */ }],
      name: [{lane: 0b0001, payload: '用户输入的值'}],
    }
  5. 调度并渲染用户输入事件所产生的那个的那个更新任务,由于接口造成的那个任务优先级较低会被暂时搁置

    //current
    states = {list: [], name: ''}
    updateQueue = {
     list: [{lane: 0b0010, payload: remoteData /* 接口返回的数据 */ }],
     name: [{lane: 0b0001, payload: '用户输入的值'}],
    }
    //workInProgress
    states = {list: [], name: '用户输入的值'}
    updateQueue = {
     list: [{lane: 0b0010, payload: remoteData /* 接口返回的数据 */ }],
     name: [],
    }
  6. 用户所产生的那个任务完成,workInProgress中的lanes,和states会成为current states

    //current
    states = {list: [], name: '用户输入的值'}
    updateQueue = {
    list: [{lane: 0b0010, payload: remoteData /* 接口返回的数据 */ }],
    name: [],
    }
  7. 渲染剩余的任务,也就是接口所产生的那个任务

    //current
    states = {list: [], name: '用户输入的值'}
    updateQueue = {
    list: [{lane: 0b0010, payload: remoteData /* 接口返回的数据 */ }],
    name: [],
    }
     
    //workInProgress
    states = {list: remoteData, name: '用户输入的值'}
    updateQueue = {
    list: [],
    name: [],
    }
  8. 接口数据也渲染完成,workInProgress中的lanes,和states会成为current states

     //current
     states = {list: remoteData, name: '用户输入的值'}
     updateQueue = {
     list: [],
     name: [],
    }
    

源码出处,如果你对源码已经有了个大体的概念,下面的关键代码处,会让你更好的理解上面的解释

撰写回答
你尚未登录,登录后可以
  • 和开发者交流问题的细节
  • 关注并接收问题和回答的更新提醒
  • 参与内容的编辑和改进,让解决方法与时俱进
推荐问题
宣传栏