c语言编程笔录

首页 >   > 笔记大全

笔记大全

给大家普及下React中的任务调度算法

更新时间:2023-10-29
React的任务调度算法是React Fiber以及React调度器两个相互配合的核心实现。现在我们来深入了解这个算法,其实React任务调度算法可以分成两个部分,Task Scheduling 和 Reconciliation 这两个环节。Task Scheduling可以理解为react的调度器,是控制何时何地执行任务的;而Reconciliation 是一次执行任务的详细计划,包括了哪些操作、更新顺序、更新优先级等,它可以看作是Task Scheduling的实现部分。接下来我们将从这两个维度分别介绍。 ## Task Scheduling(调度器) Task Scheduling是React任务调度算法的核心部分,这个部分React采用了自己实现的调度器,也就是调度队列,Scheduler。一般而言,在一个浏览器的环境中,在一次事件循环中(Event Loop)处理完成之后,浏览器会在用户输入之前尽快绘制出初始的UI,以让用户尽快反馈。而React也借鉴了这个机制,但是有一些不同,它会尽快地清除调度队列中的任务,而非一次性的清空队列。 例如执行以下代码:
setTimeout(() => {
  console.log('1', Date.now())
}, 0)
Promise.resolve()
  .then(() => {
    console.log('2', Date.now())
    setTimeout(() => {
      console.log('3', Date.now())
    }, 0)
    return Promise.resolve()
  })
  .then(() => {
    console.log('4', Date.now())
  })

console.log('end')
输出结果如下:
end
2 1617632839207
4 1617632839208
1 1617632839210
3 1617632839210
显然,React会尽快清空一次任务队列。然而,这里可能不止一次 清空 操作,因为队列中的每一个任务执行都可能会添加新的任务到队列中。为了更好地理解这个流程,我们来看下图: ![图1](https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/b390adf7646d4cfc9d0db8f1d9c0308c~tplv-k3u1fbpfcp-zoom-1.image) 当事件触发时,产生N阶段后,任务都被加入了一个初级任务队列中,例如requestAnimationFrame、requestIdleCallback等。而React的Scheduler则在这些初级任务队列之上又加了一层底层任务队列(scheduler work)。在这个底层任务队列中,处理优先级较高的任务。这个优先级是通过schedule API的优先级参数来控制的,默认值为normal。不过,React还有一个特别的优先级——sync,它的优先级最高,我们可以通过React.unstable_flushSync来调用它。所有任务都由优先级最高的函数调用开始,进一步的任务在空闲的时间内依次处理。 实际上,在同一个事件循环中,React可能会有多次处理。例如,假设在一个滚动事件监听器中,用户连续地进行频繁滑动,事件会频繁触发,使得React也会多次调度重新渲染。这个行为看似浪费,但其作用确实非常大,能够保证页面能在最短的时间内进行更新。此外,考虑到某次更新引起其他更新的情况,React还提供了一次判断就够机会去检测这部分更新可能是否和当前处理已经完成的update有冲突。如果有冲突,React会重新执行两个阶段的任务 Scheduling 和 Reconciliation。 ## Reconciliation(协调器) Task Scheduling 能够控制何时何地去执行任务,而 Reconciliation 则是一次具体的任务执行计划。它会为更新操作分配优先级,并根据更新优先级制定出任务顺序,最后执行更新操作。在更新前,React会先调用componentWillUnmount和componentDidUpdate方法,供开发者在组件销毁和更新前进行相应的逻辑处理。那么,底层的更新流程是什么样的呢?我们把它分为三个阶段。 1. build阶段 build是计算出哪些组件需要被更新的过程。这个阶段也可以理解为“diff'ing”——它会找到当前和之前的数据的差异,这些差异是React需要重新渲染的组件。这些变化通过一棵虚拟的“Fiber tree”来表示。每一个“Fiber”节点可能代表一个新的子节点或一个新组件。重点在于,React标记了每一个Fiber节点和对应组件的状态,包括了是否需要更新、是否需要卸载掉、是否为第一次渲染。这个阶段的主要流程是递归,它会更新每一个子树中的非纯组件,但不会更新纯组件。 以下是一个简单的例子,假设有以下两个组件:
class A extends React.Component {
  render() {
    return this.props.num % 2 == 0 ? (
      
    ) : (
      
    );
  }
}

class B extends React.Component {
  render() {
    return 
{this.props.num}
; } } class C extends React.Component { render() { return
{this.props.num}
; } }
此时如果执行以下代码: `ReactDOM.render(, document.body)` React就会构建以下的 Fiber tree: ``` { tag: ClassComponent, type: A, stateNode: A, memoizedProps: { num: 1 }, updateQueue: null, memoizedState: null, effectTag: 'UPDATE', nextEffect: null, firstEffect: { tag: ClassComponent, type: C, stateNode: C, memoizedProps: { num: 1 }, updateQueue: null, memoizedState: null, effectTag: 'PLACEMENT', nextEffect: null, firstEffect: null, lastEffect: null, child: null, sibling: null, index: 0 }, lastEffect: { tag: ClassComponent, type: C, stateNode: C, memoizedProps: { num: 1 }, updateQueue: null, memoizedState: null, effectTag: 'PLACEMENT', nextEffect: null, firstEffect: null, lastEffect: null, child: null, sibling: null, index: 0 }, child: { tag: ClassComponent, type: C, stateNode: C, memoizedProps: { num: 1 }, updateQueue: null, memoizedState: null, effectTag: 'PLACEMENT', nextEffect: null, firstEffect: null, lastEffect: null, child: null, sibling: null, index: 0 }, sibling: null, index: 0 } ``` 我们可以看到,第一次渲染后,A组件下的子树只有纯组件B呈现出差异,而不是直接全部替换。 2. Commit 阶段 Commit 阶段会将 Fiber tree 上标记为“EFFECT”的组件进行实际的DOM操作,比如更新、插入、删除等等。这个阶段是很激进的,会直接对DOM进行操作。例如,React 会通过DOM操作插入新元素、更新属性、删除元素、调整位置等操作。而且,这个阶段的操作是同步的,可以立即反应在屏幕上,可以说是用户交互的结果。 因为每个组件的渲染最终都会被同步调用一遍componentDidMount或componentDidUpdate,这是一个完整的生命周期回调。 3. Render 阶段 Render 阶段是重复的过程,直到已构建的 Fiber tree 所有组件都是纯组件,这个过程称为“reconciliation”(协调)。这其中还包括了协调同一父组件下的多个子组件。 在Reconciliation 阶段,React 会递归 Fiber tree,对于标记为“dirty”的 Fiber 节点执行无副作用的 render。这也是叫做“reconciliation” 这个过程的优化是要减少 render 的次数,本文前文已经说的很清楚了,而因此,React通过调整和分配优先级来决定更新的顺序。 总结 React的任务调度算法可以分成两个部分:Task Scheduling和Reconciliation。Task Scheduling通过自己实现的调度器Scheduler,控制何时何地执行任务,并且尽快清空一次任务队列。使用Fiber tree 作为中介,Reconciliation 是一次具体的任务执行计划,用于计算更新操作、分配优先级、制定任务顺序和执行更新操作。组件重渲染的过程涉及到了三个阶段:build、commit 和 render,其中 build是计算出差异和更新的过程;commit 阶段是真正意义上对DOM的操作;reconciliation 阶段是重复执行,递归fiber tree对于标记为“dirty”的fiber节点无副作用的render。

  • 个人微信

    工作时间

    周一至周日 9:00-21:00