React浅谈setState

写到前面

为何是setState,由于对于你们而言,大多数使用react的新手或者初学者,大多会直接接触到setState,并且这个方法也多是接触最多的操做方法。那么要想详细了解setState究竟在React中作了什么事情,就须要深刻了解一下。而在最新的React 16版本中,React的核心渲染框架时进行过一次升级的,由以前的React升级到了React Fiber。(PS:本文针对菜鸟、初级工程师而写,有错误不足之处,请各位大佬指出更正。感受太low,请绕道,谢谢。)前端

  • 为何会升级?
  • 为何了解Fiber?

别着急,让我来慢慢给大家解答,在16版本以前,React使用的仍是旧版的渲染核心,它的渲染过程是一口气完成,怎么理解呢?就是会一次性遍历你全部的Dom节点,这个过程取决于你的应用的复杂程度。固然,这个过程通常比较快,可是也不排除在大型复杂应用中出现比较长的等待时间,这个时间是基于ms级别的。而做为一个前端工程师,性能优化是比较重要的一方面之一,你们都知道,浏览器是的渲染引擎是单线程的,这就意味着一个时间段以内只能完成一件事。当你的应用过于复杂时,用户操做变多,弊端就显示出来了:卡顿,未响应,甚至是页面崩溃...这就是为何React会升级到React Fiber,在未升级以前,渲染模式是这样的:react

假设你的结构是这样的 A组件 => B组件 => C/D/E组件 D组件 => F组件 未使用Fiber架构的渲染方式git

他的旧版渲染模式是这样的: github

以render()函数为分界线。从顶层组件开始,一直往下,直至最底层子组件。而后再往上。组件update阶段同理。一直执行,直到完成,这个过程彻底不理你。(我喜欢叫狗不理阶段)

在升级为Fiber以后,就如同游泳同样,每一个一段时间,都须要上岸呼吸一口气,因此渲染模式就变成更了如下状况: 算法

潜水员会每隔一段时间就上岸,看是否有更重要的事情要作。数组

加入fiber的react将组件更新分为两个阶段,Reconcile阶段和Commit阶段。浏览器

  • Reconcile阶段,在这个阶段内,React经过diff算法,判断哪些组价须要更新,经须要更新的组件打上tag(标记),再将全部须要更新的组件添加到一个数组中,等待或者执行更新任务。注意:这个阶段是能够被打断的,也就是说在这个阶段内,react检测到有用户操做行为,或者是其余的一些事情都会打断,在事件执行完毕以后在从新将进行此阶段,是从新进行。
  • Commit阶段,这个阶段是根据Reconcile阶段生成的更新的数组,遍历更新DOM,这个阶段是一次性执行完毕的,而且是不会被打断的。

经过这个俩个阶段,你就会明白,为何以前会把componentWillMount、componentWillReviceProps和componentWillUpdate标记为不安全的生命周期函数了,由于在Reconcile阶段,被打断以后是从新进行的,就有可能形成对此的数据请求,对此渲染,形成没必要要的资源、性能浪费(这里有一个比较有意思饥饿问题,聪明的同窗应该已经猜出来了,react如今尚未公布解决方法哦)。安全

Fiber具体是什么样的?

Fiber实际上是一个对象。在Fiber源码中,有这么一段描述性能优化

A Fiber is work on a Component that needs to be done or was done. There can be more than one per component.bash

Fiber就是经过对象记录组件上须要作或者已经完成的更新,一个组件能够对应多个Fiber。

接下来让咱们看看Fiber具体是什么样子的?既然是一个对象,就确定是{}模式。以下:

{
    tag,
    key,
    elementType,
    type,
    stateNode,
    return,
    child,
    sibling,
    index,
    ref,
    pendingProps,
    memoizedProps,
    updateQueue,
    memoizedState,
    firstContextDependency,
    mode,
    effectTag,
    nextEffect,
    firstEffect,
    lastEffect,
    expirationTime,
    childExpirationTime,
    alternate,
    actualDuration,
    actualStartTime,
    selfBaseDuration,
    treeBaseDuration
}
复制代码

在render函数中建立的React Element树在第一次渲染的时候会建立一颗结构如出一辙的Fiber节点树。不一样的React Element类型对应不一样的Fiber节点类型。一个React Element的工做就由它对应的Fiber节点来负责。

Fiber的优先级以下:

高优先级会打断正在执行的低优先级任务先执行。

一个React Element能够对应不止一个Fiber,由于Fiber在更新的时候,会从原来的Fiber(current)克隆出一个新的Fiber(alternate)。两个Fiber diff出的变化(side effect)记录在alternate上。因此一个组件在更新时最多会有两个Fiber与其对应,在更新结束后alternate会取代以前的current的成为新的current节点。

这是fiber在目前版本v16.6.3所维护的全部属性,具体想要了解阅读源码请看这里。ReactFiber.js

setState

在官方文档中,明确指出,要把state认做是不可变的,因此,如今更推崇的写法不是直接setState,而是经过setState的回调函数进行更改。

this.setState(() => {[key]: value});
复制代码

好,不说题外话了,让咱们进入今天的正题,setState。 你们写项目的时候,在index.js文件中,会引入两个文件,react,react-dom。setState在react文件是这样的:

熟不熟悉?Conmponent类,在这里面咱们能够看看干了什么事情,接受props,context和updater,注意我拿红线标出来的部分,短路运算,再看看注释,这个updater是随后注入进去的。先不论是何时注入进去的,让咱们接着往下看,setState确定会触发更新,那咱们就沿着this.updater往下走,去寻找ReactNoopUpdateQueue(react空操做更新队列),不少人会犯嘀咕,都空操做了还要更新什么?耐心点,这里的确是不进行任何更新操做,只是验证一个数据格式,和检验旧版V8引擎的一些错误,并抛出来。
这是什么?setState?干了什么?参数校验,若是经过就执行下面的方法,this指的当前实例。
在enqueueSetState方法中,也是实例验证。验证明例是否mounted。

在你的应用第一次渲染的时候,最主要的是关注react-dom的进行,前面说过updater是随后注入进去的,就是在react-dom加载的时候注入进去的。接下来,setState带你们去看看到底是什么?

直接来看setState队列,这里须要3个参数能够看到分别是实例对象,载荷和回调函数。在这里咱们先看在最开始生命4个变量分别是干什么用的,直接语义化就能猜出个大概来。

Q1:fiber经过get方法获取一些东西?

A1: 能够看到,源代码实现的方法,获再结合当前调用方法的上下文能够得知,当前的fiber获取到时当前实例上的一个_reactInternalFiber的值。这个值是什么,实际上是经过相应的一个set方法,将当前实例和workInProgress传入,并给赋值给当前实例的_reactInternalFiber属性。

Q2:currentTime获取当前的时间?

A2:

  • 首先判断是否正在渲染中,是的话就返回最近一次的调度时间
  • 若是不在渲染中的话,会检查是否有上次遗留的待处理的工做。
  • 若是nextFlushedExpirationTime === NoWork || nextFlushedExpirationTime === Never,来判断优先级。
  • 从新计算当前的渲染时做为调度时间,而且return;
  • 若是上次有遗留,则直接返回当前调度时间。
  • rederingTime 能够随时更新,currentSechedulerTime只有在没有新任务的时候才更新

Q3:expirationTime获取到期时间?什么鬼?

A3:

  • 在此时,会进入第一个if条件判断,经过判断当前是否存在正在执行的上下文时间,是否正在进行渲染,仍是其余状况。
  • 若是存在expirationContext,则到期时间就是修改成当前的上下文执行时间。
  • 若是正在调度时间的话,判断是否处于commit阶段,是的话就设置为同步优先级,不然的话就赋值为下次渲染到期时间。
  • 若是上述状况都不知足的状况下,就会计算当前实例fiber的优先级。
  • 这里分为异步和同步,分别调用不一样的方法进行计算,得到优先级后则和同步更新同样, 建立update并放进队列, 而后调用sheuduleWork
  • 在这里还会有交互式刷新的判断,是追踪最短待处理的交互式到期时间。 这容许咱们在须要时同步刷新全部交互式更新。
  • 最后返回当前所须要的到期时间。
  • 此步骤和2步骤能够合并为计算优先级

Q4:update建立update队列?

A4: 这个阶段就是经过createUpdate来建立一个更新对象。

在进行了一系列不可描述的过程以后,终于能够进行接下来的操做了。

首先调用flushPassiveEffects()来进行刷新,将被动影响的属性刷新一遍,接着是重头戏,调用enqueueUpdate()方法,将须要更新的fiber放入更新队列。

这里其实就是这么个原理:

第一部分

  1. 首先判断是否是只有一个fiber,只有一个fiber的话就让q1等于这个值,而后q2克隆q1
  2. 若是是有俩个fiber,则q1等于当前实例的fiber.updateQueue,q2就等于alternate.updateQueue;
  3. 若是两个fiber都没有更新队列。则q1,q2都建立新的。
  4. 只有一个fiber有更新队列。克隆以建立一个新的。
  5. 俩个fiber都有更新队列。总之就是,q1和q2都须要有一个fiber。

第二部分

  1. 当q1与q2是相等时,一位置实际上只有一个fiber,将此fiber插入到更新队列;
  2. 若q1和q2有一个是非空队列,则两个对列都须要更新;
  3. 当q1和q2两个队列都是非空,因为结构共享,两个列表中的最后一次更新是相同的。所以,只需q1添加到更新队列便可;
  4. 最后将q2的lastUpdate指针更新。

最后一步,就是掉用scheduleWork()方法,来进行最后的更新。在此方法中会根据优先级进行分片式更新。

  1. 首先调用scheduleWorkToRoot()方法,更新fiber的优先级,遍历到根组件的父级路径,并更新子组件的优先级。
  2. 为先前未计划的交互更新挂起的异步工做计数。
  3. 更新当前交互的挂起的异步工做计数。
  4. 监听更新列表的变化,返回root。

接下来,在commit阶段,一口气执行完毕。你的DOM就是最新的了。说了这么多,可能执行起来,就是短短的几十毫秒... 就好比下面

image

至此,setState整个过程算是完成了。

总结:这篇文章是鄙人第一次下手书写,有些地方可能表述不是很准确,可能有点啰嗦,可是我喜欢啊。俗话说万事开头难,可是过程也难啊,结果更难啊。对于代码也同样,要坚持下去,坚持下去你就得颈椎病了哦。本文有什么错误的地方,还烦请各路大神指出,鄙人是不会改滴,都会记在内心哒,上述是我对setState的理解,抛砖引玉,但愿帮助你们有方向的去了解react原理机制。

相关文章
相关标签/搜索