内容简介:为什么是setState,因为对于大家而言,大多数使用react的新手或者初学者,大多会直接接触到setState,而且这个方法也可能是接触最多的操作方法。那么要想详细了解setState究竟在React中做了什么事情,就需要深入了解一下。而在最新的React 16版本中,React的核心渲染框架时进行过一次升级的,由之前的React升级到了React Fiber。(PS:本文针对菜鸟、初级工程师而写,有错误不足之处,请各位大佬指出更正。感觉太low,请绕道,谢谢。)别着急,让我来慢慢给你们解答,在16版
为什么是setState,因为对于大家而言,大多数使用react的新手或者初学者,大多会直接接触到setState,而且这个方法也可能是接触最多的操作方法。那么要想详细了解setState究竟在React中做了什么事情,就需要深入了解一下。而在最新的React 16版本中,React的核心渲染框架时进行过一次升级的,由之前的React升级到了React Fiber。(PS:本文针对菜鸟、初级工程师而写,有错误不足之处,请各位大佬指出更正。感觉太low,请绕道,谢谢。)
- 为什么会升级?
- 为什么了解Fiber?
别着急,让我来慢慢给你们解答,在16版本之前,React使用的还是旧版的渲染核心,它的渲染过程是一口气完成,怎么理解呢?就是会一次性遍历你所有的Dom节点,这个过程取决于你的应用的复杂程度。当然,这个过程一般比较快,但是也不排除在大型复杂应用中出现比较长的等待时间,这个时间是基于ms级别的。而作为一个前端工程师,性能优化是比较重要的一方面之一,大家都知道,浏览器是的渲染引擎是单线程的,这就意味着一个时间段之内只能完成一件事。当你的应用过于复杂时,用户操作变多,弊端就显示出来了:卡顿,未响应,甚至是页面崩溃...这就是为什么React会升级到React Fiber,在未升级之前,渲染模式是这样的:
假设你的结构是这样的 A组件 => B组件 => C/D/E组件 D组件 => F组件 未使用Fiber架构的渲染方式
他的旧版渲染模式是这样的:
以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.
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放入更新队列。
这里其实就是这么个原理:
第一部分
- 首先判断是不是只有一个fiber,只有一个fiber的话就让q1等于这个值,然后q2克隆q1
- 如果是有俩个fiber,则q1等于当前实例的fiber.updateQueue,q2就等于alternate.updateQueue;
- 如果两个fiber都没有更新队列。则q1,q2都创建新的。
- 只有一个fiber有更新队列。克隆以创建一个新的。
- 俩个fiber都有更新队列。总之就是,q1和q2都需要有一个fiber。
第二部分
- 当q1与q2是相等时,一位置实际上只有一个fiber,将此fiber插入到更新队列;
- 若q1和q2有一个是非空队列,则两个对列都需要更新;
- 当q1和q2两个队列都是非空,由于结构共享,两个列表中的最后一次更新是相同的。因此,只需q1添加到更新队列即可;
- 最后将q2的lastUpdate指针更新。
最后一步,就是掉用scheduleWork()方法,来进行最后的更新。在此方法中会根据优先级进行分片式更新。
- 首先调用scheduleWorkToRoot()方法,更新fiber的优先级,遍历到根组件的父级路径,并更新子组件的优先级。
- 为先前未计划的交互更新挂起的异步工作计数。
- 更新当前交互的挂起的异步工作计数。
- 监听更新列表的变化,返回root。
接下来,在commit阶段,一口气执行完毕。你的DOM就是最新的了。说了这么多,可能执行起来,就是短短的几十毫秒... 就比如下面
至此,setState整个过程算是完成了。
总结:这篇文章是鄙人第一次下手书写,有些地方可能表述不是很准确,可能有点啰嗦,但是我喜欢啊。俗话说万事开头难,但是过程也难啊,结果更难啊。对于代码也一样,要坚持下去,坚持下去你就得颈椎病了哦。本文有什么错误的地方,还烦请各路大神指出,鄙人是不会改滴,都会记在心里哒,上述是我对setState的理解,抛砖引玉,希望帮助大家有方向的去了解react原理机制。
以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网
猜你喜欢:本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
HTTP权威指南
David Gourley、Brian Totty / 陈涓、赵振平 / 人民邮电出版社 / 2012-9 / 109.00元
超文本转移协议(Hypertext Transfer Protocol,HTTP)是在万维网上进行通信时所使用的协议方案。HTTP有很多应用,但最著名的是用于web浏览器和web服务器之间的双工通信。 HTTP起初是一个简单的协议,因此你可能会认为关于这个协议没有太多好 说的。但现在,你手上拿着的是却一本两磅重 的书。如果你对我们怎么会写出一本650页 的关于HTTP的书感到奇怪的话,可以去......一起来看看 《HTTP权威指南》 这本书的介绍吧!