React源码state计算流程和优先级实例解析

目录
  • setState执行之后会发生什么
    • 根据组件实例获取其 Fiber 节点
    • 创建update对象
    • 将Update对象关联到Fiber节点的updateQueue属性
    • 发起调度
  • processUpdateQueue做了什么
    • 变量解释
    • 构造本轮更新的 updateQueue
    • 更新 workInProgress 节点
  • 总结
    • update对象丢失问题
      • 为什么会丢失
      • 如何解决
    • state计算的连续性
      • 问题现象
      • 如何解决

setState执行之后会发生什么

setState 执行之后,会执行一个叫 enqueueSetState 的方法,这个主要作用是创建 Update 对象和发起调度,可以看下这个函数的逻辑,

enqueueSetState: function (inst, payload, callback) {
    // 1. inst是组件实例,从组件实例中拿到当前组件的Fiber节点
    var fiber = get(inst);
    var eventTime = requestEventTime();
    var lane = requestUpdateLane(fiber);
    // 2.1 根据更新发起时间、优先级、更新的payload创建一个update对象
    var update = createUpdate(eventTime, lane);
    update.payload = payload;
    // 2.2 如果 setState 有回调,顺便把回调赋值给 update 对象的 callback 属性
    if (callback !== undefined && callback !== null) {
      update.callback = callback;
    }
    // 3. 将 update 对象关联到 Fiber 节点的 updateQueue 属性中
    enqueueUpdate(fiber, update);
    // 4. 发起调度
    var root = scheduleUpdateOnFiber(fiber, lane, eventTime);
}

从上面源码可以清晰知道,setState 调用之后做的4件事情

  • 根据组件实例获取其 Fiber 节点
  • 创建 Update 对象
  • Update 对象关联到 Fiber 节点的 updateQueue 属性中
  • 发起调度

根据组件实例获取其 Fiber 节点

其实就是拿组件实例中的 _reactInternals 属性,这个就是当前组件所对应的 Fiber 节点

function get(key) {
  return key._reactInternals;
}

题外话:react利用双缓存机制来完成 Fiber 树的构建和替换,也就是 currentworkInProgress 两棵树,那 enqueueSetState 里面拿的是那棵树下的 Fiber 节点呢?

答案是:current树下的Fiber节点。具体的原理在下面update对象丢失问题再说明

创建update对象

function createUpdate(eventTime, lane) {
  var update = {
    eventTime: eventTime,
    lane: lane,
    tag: UpdateState,
    payload: null,
    callback: null,
    next: null
  };
  return update;
}

属性的含义如下:

  • eventTime:update对象创建的时间,用于ensureRootIsScheduled计算过期时间用
  • lane:此次更新的优先级
  • payload:setState的第一个参数
  • callback:setState的第二个参数
  • next:连接的下一个 update 对象

将Update对象关联到Fiber节点的updateQueue属性

这里执行的是 enqueueUpdate 函数,下面是我简化过后的逻辑

function enqueueUpdate(fiber, update) {
    var updateQueue = fiber.updateQueue;
    var sharedQueue = updateQueue.shared;
    var pending = sharedQueue.pending;
    if (pending === null) {
      update.next = update;
    } else {
      update.next = pending.next;
      pending.next = update;
    }
    sharedQueue.pending = update;
}

可以看到这里的逻辑主要是将 update 对象放到 fiber 对象的 updateQueue.shared.pending 属性中, updateQueue.shared.pending 是一个环状链表。

那为什么需要把它设计为一个环状链表?我是这样理解的

  • shared.pending 存放的是链表的最后一个节点,那么在环状链表中,链表的最后一个节点的next指针,是指向环状链表的头部节点,这样我们就能快速知道链表的首尾节点
  • 当知道首尾节点后,就能很轻松的合并两个链表。比如有两条链表a、b,我们想要把 b append到 a 的后面,可以这样做
const lastBPoint = bTail
const firstBPoint = bTail.next
lastBPoint.next = null
aTail.next = firstBPoint
aTail = lastBPoint

后面即使有c、d链表,同样也可以用相同的办法合并到a。react 在构建 updateQueue 链表上也用了类似的手法,新产生的 update 对象通过类似上面的操作合并到 updateQueue 链表,

发起调度

enqueueUpdate 末尾,执行了 scheduleUpdateOnFiber 函数,该方法最终会调用 ensureRootIsScheduled 函数来调度react的应用根节点。

当进入 performConcurrentWorkOnRoot 函数时,就代表进入了 reconcile 阶段,也就是我们说的 render 阶段。render 阶段是一个自顶向下再自底向上的过程,从react的应用根节点开始一直向下遍历,再从底部节点往上回归,这就是render阶段的节点遍历过程。

这里我们需要知道的是,在render阶段自顶向下遍历的过程中,如果遇到组件类型的Fiber节点,我们会执行 processUpdateQueue 函数,这个函数主要负责的是组件更新时 state 的计算

processUpdateQueue做了什么

processUpdateQueue函数主要做了三件事情

  • 构造本轮更新的 updateQueue,并缓存到 currentFiber 节点中
  • 循环遍历 updateQueue,计算得到 newState,构造下轮更新的 updateQueue
  • 更新 workInProgress 节点中的 updateQueuememoizedState 属性

这里的 updateQueue 并不指代源码中 Fiber 节点的 updateQueue,可以理解为从 firstBaseUpdatelastBaseUpdate 的整条更新队列。这里为了方便描述和理解,直接用 updateQueue 替代说明。

变量解释

因为涉及的变量比较多,processUpdateQueue 函数的逻辑看起来并不怎么清晰,所以我先列出一些变量的解释方便理解

  • shared.pending:

enqueueSetState 产生的 update对象 环形链表

  • first/lastBaseUpdate:-- 下面我会用 baseUpdate 代替

当前 Fiber 节点中 updateQueue 对象中的属性,代表当前组件整个更新队列链表的首尾节点

  • first/lastPendingUpdate:下面我会用 pendingUpdate 代替

shared.pending 剪开后的产物,分别代表新产生的 update对象 链表的首尾节点,最终会合并到 currentFiber 和 workInProgress 两棵树的更新队列尾部

  • newFirst/LastBaseUpdate:下面我会用 newBaseUpdate 代替

newState计算过程会得到,只要存在低优先级的 update 对象,这两个变量就会有值。这两个变量会赋值给 workInProgress 的 baseUpdate,作为下一轮更新 update对象 链表的首尾节点

  • baseState:newState 计算过程依赖的初始 state
  • memoizedState:当前组件实例的 state,processUpdateQueue 末尾会将 newState 赋值给这个变量,

构造本轮更新的 updateQueue

上面我们说到 shared.pendingenqueueSetState 产生的 update对象 环形链表,在这里我们需要剪断这个环形列表取得其中的首尾节点,去组建我们的更新队列。那如何剪断呢?

shared.pending 是环形链表的尾部节点,它的下一个节点就是环形链表的头部节点,参考上一小节我们提到的链表合并操作。

var lastPendingUpdate = shared.pending;
var firstPendingUpdate = lastPendingUpdate.next;
lastPendingUpdate.next = null;

这样就能剪断环形链表,拿到我们想要的新的 update 对象 —— pendingUpdate。接着我们要拿着这个 pendingUpdate 做两件事情:

  • pendingUpdate 合并到当前Fiber节点的更新队列
  • pendingUpdate 合并到 currentFiber树 中对应 Fiber节点 的更新队列

为什么要做这两件事情?

  • 第一个是解决状态连续性问题,当出现多个 setState 更新时,我们要确保当前 update对象 的更新是以前一个 update对象 计算出来的 state 为前提。所以我们需要构造一个更新队列,新的 update对象 要合并到更新队列的尾部,从而维护state计算的连续性
  • 第二个是解决 update 对象丢失问题。在 shared.pending 被剪开之后,shared.pending会被赋值为null,当有高优先级任务进来时,低优先级任务就会被打断,也就意味着 workInProgress 树会被还原,shared.pending 剪开之后得到的 pendingUpdate 就会丢失。这时就需要将 pendingUpdate 合并到 currentFiber树 的更新队列中

接下来可以大致看一下这一部分的源码

  var queue = workInProgress.updateQueue;
  var firstBaseUpdate = queue.firstBaseUpdate;
  var lastBaseUpdate = queue.lastBaseUpdate;
  // 1. 先拿到本次更新的 update对象 环形链表
  var pendingQueue = queue.shared.pending;
  if (pendingQueue !== null) {
    // 2. 清空pending
    queue.shared.pending = null;
    var lastPendingUpdate = pendingQueue;
    var firstPendingUpdate = lastPendingUpdate.next;
    // 3. 剪开环形链表
    lastPendingUpdate.next = null;
    // 4. 将 pendingupdate 合并到 baseUpdate
    if (lastBaseUpdate === null) {
      firstBaseUpdate = firstPendingUpdate;
    } else {
      lastBaseUpdate.next = firstPendingUpdate;
    }
    lastBaseUpdate = lastPendingUpdate;
    // 5. 将 pendingupdate 合并到 currentFiber树的 baseUpdate
    var current = workInProgress.alternate;
    if (current !== null) {
      var currentQueue = current.updateQueue;
      var currentLastBaseUpdate = currentQueue.lastBaseUpdate;
      if (currentLastBaseUpdate !== lastBaseUpdate) {
        if (currentLastBaseUpdate === null) {
          currentQueue.firstBaseUpdate = firstPendingUpdate;
        } else {
          currentLastBaseUpdate.next = firstPendingUpdate;
        }
        currentQueue.lastBaseUpdate = lastPendingUpdate;
      }
    }
  }

源码看起来很多,但本质上只做了一件事,从源码中可以看出这部分主要就是把 shared.pending 剪开,拿到我们的 pendingUpdate,再把 pendingUpdate 合并到本轮更新和 currentFiber 节点的 baseUpdate 中。

计算 newState

在这部分的源码中,除了计算 newState,还有另外一个重要工作是,构造下一轮更新用的 updateQueue

到这里可能会有疑问,为什么需要构造下轮更新的 updateQueue,本轮更新我们把 shared.pending 里面的对象遍历计算完,再把 state 更新,下轮更新进来再根据这个 state 计算不行好了吗?

如果没有高优先级任务打断机制,确实是不需要在这里构造下轮更新的 updateQueue,因为每轮更新我们只会依赖当前的 state 和 shared.pending

打断机制下,低优先级任务重启后的执行,需要依赖完整的更新队列才能保证 state 的连续性和正确性。下面我举个例子

state = { count: 0 }
componentDidMount() {
    const button = this.buttonRef.current
    // 低优先级任务
    setTimeout(() => this.setState({ count: 1 }), 1000)
    // 高优先级任务
    setTimeout(() => button.click(), 1040)
}
handleButtonClick = () => {
    this.setState( prevState => {
      return { count: prevState.count + 2 }
    } )
}

我们期望能实现的效果是 0 -> 2 -> 3,需求如下:

  • 高优先级任务打断低优先级任务之后,不以低优先级任务计算得到的baseState做计算
  • 低优先级任务重启后,不能覆盖高优先级任务计算得到的值,且需要根据低优先级任务计算得到的newState,作为高优先级的baseState再去执行一次高优先级任务

知道了需求,我们可以大概列一下实现思路:

  • 低优先级任务打断后,高优先级任务执行之前,需要还原到低优先级任务执行之前的 workInPregress 节点,确保不受低优先级任务计算得到的 baseState 影响
  • 需要维护一个更新对象队列,按执行顺序存储 update 对象,确保低优先级重启后,依然会执行高优先级任务

上面说的需求和实现思路在 react 的源码中实现其实是非常简单的,但要理解其中的含义可能需要费点功夫,下面可以看看我改动过后的源码,可以直接从 do...while 开始看

  function cloneUpdate(update) {
      return {
          eventTime: update.eventTime,
          lane: update.lane,
          tag: update.tag,
          payload: update.payload,
          callback: update.callback,
          next: null
      };
  }
  if (firstBaseUpdate !== null) {
    var newState = queue.baseState;
    var newBaseState = null;
    var newFirstBaseUpdate = null;
    var newLastBaseUpdate = null;
    var update = firstBaseUpdate;
    // 遍历 updateQueue
    do {
      var updateLane = update.lane;
      var updateEventTime = update.eventTime;
      // 校验当前 update 对象够不够优先级
      if (!isSubsetOfLanes(renderLanes, updateLane)) {
        // 优先级不够,我们需要从当前 update 对象开始重新构造一个更新队列
        var clone = cloneUpdate(update)
        if (newLastBaseUpdate === null) {
          newFirstBaseUpdate = newLastBaseUpdate = clone;
          // 当前的 newState 就作为下轮更新的 baseState 使用
          newBaseState = newState;
        } else {
          newLastBaseUpdate = newLastBaseUpdate.next = clone;
        }
      } else {
        // 优先级够
        if (newLastBaseUpdate !== null) {
          // newLastBaseUpdate 不为空,就代表存在优先级不够的 update 对象
          var _clone = cloneUpdate(update)
          // 为保证状态连续性,即使当前 update 对象优先级足够,也要被放到 updateQueue 中
          newLastBaseUpdate = newLastBaseUpdate.next = _clone;
        }
        // 计算newState
        newState = getStateFromUpdate(workInProgress, queue, update, newState, props, instance);
      }
      update = update.next;
    } while (update);

逻辑如下:

优先级不够

  • 重新构造更新队列 newBaseUpdate,留到低优先级任务重启遍历
  • 记录当前 newState,留到低优先级任务重启作为 baseState 计算

优先级足够

  • 看看 newBaseUpdate 有没有东西,有东西就把当前 update 对象也合并进去
  • 计算 newState

这里 newState 的计算逻辑很简单

  • payload是值。用对象包裹合并到 prevState 即可
  • payload是函数。传入 prevState 计算,将函数返回值也合并到 prevState 即可

更新 workInProgress 节点

更新 workInProgress 节点属性的逻辑不多,主要就是把 newBaseState、newBaseUpate 赋值给 workInProgress 节点,作为下一轮更新的 baseState 和更新队列使用

if (newLastBaseUpdate === null) {
  newBaseState = newState;
}
queue.baseState = newBaseState;
queue.firstBaseUpdate = newFirstBaseUpdate;
queue.lastBaseUpdate = newLastBaseUpdate;
workInProgress.memoizedState = newState;
  • 如果 newLastBaseUpdate 为空,代表所有 update 对象为空,本轮更新计算得到的 newState 可以完全作为下轮更新的 baseState 使用。否则只能用出现首个不够优先级的 update 对象时缓存下来的 newState 作为下轮更新的 baseState
  • 更新 baseUpdate,当所有 update 对象优先级足够,baseUpdate 的值一般为空。只有存在优先级不够的 update 对象时,才会有值
  • newState 赋值给 memoizedStatememoizedState 代表当前组件的所有 state

总结

看到上面的原理解析是不是很复杂,我们可以忽略所有的实现细节,回归现象本质,state计算就是遍历 update对象 链表根据 payload 得到新的state。在此前提下,因为优先级机制,打断之后会还原 workInProgress 节点,从而会引起 update对象 丢失问题 和 state计算连续性问题。解决这两个问题才是我们上面说的复杂的实现细节

update对象丢失问题

为什么会丢失

我们知道高优先级任务进来会打断低优先级任务的执行,打断之后会将当前的 workInProgress 节点还原为开始的状态,也就是可以理解为会将 workInProgress 树还原为当前页面所渲染的 currentFiber 节点。当 workInProgress 节点还原之后,我们本来存在 workInProgress 中的 updateQueue 属性也会被重置,那就意味着低优先级的 update 对象会丢失。

上面说到的,setState产生的新 update对象 是会放在 currentFiber 节点上也是这个原因,如果 setState 产生的新 update对象 放到 workInProgress 上,只要 workInProgress 被还原,这些 update对象 就会丢失

如何解决

我们在 processUpdateQueue 函数的开始阶段,将新产生的 update 对象,也就是 shared.pending 中的值,合并到 currentFiber( workInProgress.alternate ) 节点的 firstBaseUpdatelastBaseUpdate。具体规则如下

  • currentFiber 节点不存在 lastBaseUpdate,将新的 update 对象赋值给 currentFiber 节点的 firstBaseUpdatelastBaseUpdate 属性
  • currentFiber 节点存在 lastBaseUpdate,将新的 update 对象拼接到 currentFiber 节点的 lastBaseUpdate 节点后面,也就是说新的 update 对象会成为 currentFiber 节点新的 lastBaseUpdat 节点

还原 workInProgress 节点执行的函数是 prepareFreshStack,里面会用 currentFiber 节点的属性覆盖 workInProgress 节点,从而实现还原功能。所以就算 workInProgress 节点被重置,我们只要把 update对象 合并到 currentFiber 节点上,还原的时候依然会存在于新的 workInProgress 节点

state计算的连续性

问题现象

我们上面说到,低优先级任务重启,不能覆盖高优先级任务计算得到的值,且需要根据低优先级任务计算得到的newState,作为高优先级的baseState再去执行一次高优先级任务。什么意思呢这是?

state = { count: 0 }
componentDidMount() {
    const button = this.buttonRef.current
    // 低优先级任务 - AUpate
    setTimeout(() => this.setState({ count: 1 }), 1000)
    // 高优先级任务 - BUpdate
    setTimeout(() => button.click(), 1040)
}
handleButtonClick = () => {
    this.setState( prevState => {
      return { count: prevState.count + 2 }
    } )
}

上面代码所产生的update对象如下

AUpate = { lane: 低, payload: 1 }
BUpdate = { lane: 高, payload: state => ({ count: state.count + 2 }) }
  • 先执行 AUpdate 任务
  • AUpdate 的优先级比 BUpdate 的低,BUpdate 会打断 AUpdate 的执行。
  • 那么 BUpdate 执行完,count的值为2 问题来了
  • BUpdate 是后进来的,AUpdate 不能覆盖掉 BUpdate 的结果
  • AUpdate 执行的结果 count 会变成 1,那么 BUpdate 的结果需要在此基础上计算,也就是要得到3

这也就决定了我们要用队列的形式去存储所有 update对象。update对象的存储顺序决定了state计算的前后依赖性,从而保证状态的连续性和准确性

明确很重要的一点,优先级高低只会影响某个 update对象 是否会提前执行,不会影响最终的 state 结果。最终的 state 结果还是由更新队列中 update对象 的顺序决定的

如何解决

我们看到 processUpdateQueue 中有两部分都是在构造更新队列的

  • 一部分是位于函数开头的,将 update对象 合并到 currentFiber 节点
  • 一部分是位于函数末尾的,将 newBaseUpdate 赋值给 workInProgress 节点 这两部分双剑合璧就完美解决我们的需求,currentFiber 是作用于本轮更新,workInProgress 则作用于下一轮更新,因为双缓存机制的存在,在 commit阶段 结尾,react 应用根节点的 current 指针就会指向 workInProgress 节点,workInProgress 节点在下一轮更新就会变成 currentFiber 节点。

这样无论是什么优先级,只要按顺序构造出更新队列,我就能计算出正确的newState,同时利用队列的性质,保证 update对象 间 state计算 的连续性

以上就是React源码state计算流程和优先级实例解析的详细内容,更多关于React state计算流程优先级的资料请关注我们其它相关文章!

(0)

相关推荐

  • React 中state与props更新深入解析

    目录 正文 组件的 updater 处理 ClickCounter Fiber 的 update beginWork Reconciling children for the ClickCounter Fiber 处理 Span Fiber 的 update Reconciling children for the span fiber Effects list commit 阶段 应用 effects DOM updates 调用 Post-mutation 生命周期 正文 在这篇文章中,我使

  • React组件三大属性之state,props,refs

    目录 1.1基本理解和使用 1.1.1 使用React开发者工具调试 1.1.2 定义组件的方式 1.1.3 注意 1.1.4 渲染类组件标签的基本流程 1.2 组件实例的三大核心属性之一:state 1.2.1 理解 1.2.2 案例 1.2.3 在类式组件使用state 1.2.4 在类式组件使用state的简写方式 1.2.3 强烈注意 1.3 组件实例的三大核心属性之一:props 1.3.1 理解 1.3.3 作用 1.3.4 在类式组件使用props 1.3.5 在函数式组件使用pr

  • React18 useState何时执行更新及微任务理解

    目录 函数式组件中的useState 测试1 测试2 类组件中的setState setState在promise之前定义 setState在promise之后定义 结论 函数式组件中的useState 前言:众所周知useState是异步的,但网络上对于useState何时异步更新并没有一个共识,为了探究,做一个简单的实验,实验目的是探究useState与微任务.宏任务的先后关系 测试1 实验步骤:点击按钮触发事件,在事件中我们做三件事:修改state/定义一个宏任务setTimeout /定

  • React组件实例三大属性state props refs使用详解

    目录 一. State 1.概念 2.State的简单用法 3. JS绑定事件 4.react 绑定事件 5.react this指向问题 6.修改state值 7.代码简写 二.props 1.概念 2.传参的基础方法.运算符传参 三.refs 定义 字符串形式的ref.回调函数下ref.createRef 创建ref容器 一. State 1.概念 概念:state是组件对象最重要的属性,值是对象(可以包含多个key:value的组合),组件被称为状态机,通过更新组件的state来更新对应的

  • React props和state属性的具体使用方法

    在上一节中,我们讲到了React组件,说了如何使用ES6类创建一个React组件并在其他的地方使用它.这一节我们将讲到React组件的两大灵魂--props和state. props 不知道大家还记不记得xml标签中的属性,就像这样: <class id="1"> <student id="1">John Kindem</student> <student id="2">Alick Ice</

  • vue和react中props变化后如何修改state

    目录 vue和react中props变化后修改state 改进 react改变state必须知道的知识点 1.不能直接修改state 2.state的更新是异步的 3.state的更新是一个合并的过程 state与不可变对象 vue和react中props变化后修改state 如果只想在 state 更改时重新计算某些数据,比如搜索框案例. vue <template> <div> <input type="text" v-model="filt

  • React使用useEffect解决setState副作用详解

    目录 介绍一下API fetch()方法访问API setState的副作用 使用useEffect解决这个问题 使用useEffect操控函数运行 介绍一下API 本文主要内容:描述了setState与fetch之间产生的冲突副作用,并使用useEffect进行解决 API,即Application Programming Interface,应用程序接口,是很多程序向开发人员提供的易于使用的抽象化的代码. 比如经常会用到的查询天气API,智能识图API,如果是直接照着复杂的代码编写,会相当不

  • React源码state计算流程和优先级实例解析

    目录 setState执行之后会发生什么 根据组件实例获取其 Fiber 节点 创建update对象 将Update对象关联到Fiber节点的updateQueue属性 发起调度 processUpdateQueue做了什么 变量解释 构造本轮更新的 updateQueue 更新 workInProgress 节点 总结 update对象丢失问题 为什么会丢失 如何解决 state计算的连续性 问题现象 如何解决 setState执行之后会发生什么 setState 执行之后,会执行一个叫 en

  • JDK源码Enum类原理及代码实例解析

    正文 一 概述 枚举类型是 JDK 5 之后引进的一种非常重要的引用类型,可以用来定义一系列枚举常量,使用 enum 来表示枚举可以更好地保证程序的类型安全和可读性 实际上在使用关键字enum创建枚举类型并编译后,编译器会为我们生成一个相关的类,这个类继承了Java API中的java.lang.Enum类, 也就是说通过关键字enum创建枚举类型在编译后事实上也是一个类类型而且该类继承自java.lang.Enum类 使用举例 public class EnumTest { enum MyCo

  • react源码层分析协调与调度

    目录 requestEventTime requestUpdateLane findUpdateLane lanePriority LanePriority createUpdate enqueueUpdate 总结 协调与调度 reconciler流程 同步任务类型执行机制 异步任务类型执行机制 shouldYield performUnitOfWork beginWork completeUnitOfWork scheduler流程 performWorkUntilDeadline 总结 r

  • react 源码中位运算符的使用详解

    位运算符基本使用 按位与(&):a & b对于每一个比特位,两个操作数都为 1 时, 结果为 1, 否则为 0 按位或(|):a | b对于每一个比特位,两个操作数都为 0 时, 结果为 0, 否则为 1 按位异或(^):a ^ b对于每一个比特位,两个操作数相同时, 结果为 1, 否则为 0 按位非(~):~ a反转操作数的比特位, 即 0 变成 1, 1 变成 0 0000 0000 0000 0000 0000 0000 0000 0011 -> 3 1111 1111 111

  • react源码层探究setState作用

    目录 前言 为什么setState看起来是异步的 从first paint开始 触发组件更新 更新渲染fiber tree 写在最后 前言 在深究 React 的 setState 原理的时候,我们先要考虑一个问题:setState 是异步的吗? 首先以 class component 为例,请看下述代码(demo-0) class App extends React.Component { state = { count: 0 } handleCountClick = () => { this

  • 详细聊聊React源码中的位运算技巧

    目录 前言 几个常用位运算 按位与(&) 按位或(|) 按位非(-) 标记状态 优先级计算 总结 前言 这两年有不少朋友和我吐槽React源码,比如: 调度器为什么用小顶堆这种数据结构,直接用数组不行? 源码里各种单向链表.环状链表,直接用数组不行? 源码里各种位运算,有必要么? 作为业务依赖的框架,为了提升一点点运行时性能,React从不吝惜将源码写的很复杂. 在涉及状态.标记位.优先级操作的地方大量使用了位运算. 本文会讲解其中比较有代表性的部分.学到之后,当遇到类似场景时露一手,你就是业务

  • 深入分析React源码中的合成事件

    目录 热身准备 明确几个概念 事件系统角色划分 事件注册 registerSimpleEvents registerEvents$2 registerEvents$1 registerEvents$3 registerEvents 事件监听 事件派发 合成事件 捕获和冒泡 总结 热身准备 明确几个概念 在React@17.0.3版本中: 所有事件都是委托在id = root的DOM元素中(网上很多说是在document中,17版本不是了): 在应用中所有节点的事件监听其实都是在id = root

  • react源码合成事件深入解析

    目录 引言 导火线 事件委托 合成事件特点 React 事件系统 事件注册 enqueuePutListener() listenTo() trapCapturedEvent 与 trapBubbledEvent 事件存储 事件分发 事件执行 构造合成事件 批处理 引言 温馨提示: 下边是对React合成事件的源码阅读,全文有点长,但是!如果你真的想知道这不为人知的背后内幕,那一定要耐心看下去! 最近在做一个功能,然后不小心踩到了 React 合成事件 的坑,好奇心的驱使,去看了 React 官

  • 细说webpack源码之compile流程-入口函数run

    Webpack是目前基于React和Redux开发的应用的主要打包工具.我想使用Angular 2或其他框架开发的应用也有很多在使用Webpack. 本节流程如图: 现在正式进入打包流程,起步方法为run: Compiler.prototype.run = (callback) => { const startTime = Date.now(); const onCompiled = (err, compilation) => { /**/ }; this.applyPluginsAsync(

  • React 源码调试方式

    目录 正文 断点调试 搜索定位 Chrome Devtools 调试 sourcemap npm 下载react包 插件注释 调试 React 最初源码 关联 react 源码项目 总结 正文 什么?调试 React 源码还有优雅和不优雅之分? 别着急,我们先来听个故事: 东东是一名前端工程师,主要用 React 技术栈,用了多年之后想深入一下,所以最近开始看 React 源码. 断点调试 他把 react 和 react-dom 包下载了下来,在项目里引入,开发服务跑起来后,打开 Chrome

随机推荐