React之setState原理分析
source link: http://blog.poetries.top/2018/12/20/react-setState/?amp%3Butm_medium=referral
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
- 我们都知道,
React
通过this.state
来访问state
,通过this.setState()
方法来更新state
。当this.setState()
方法被调用的时候,React
会重新调用render
方法来重新渲染UI
- 首先如果直接在
setState
后面获取state
的值是获取不到的。在React
内部机制能检测到的地方,setState
就是异步的;在React
检测不到的地方,例如setInterval
,setTimeout
,setState
就是同步更新的
因为 setState
是可以接受两个参数的,一个 state
,一个回调函数。因此我们可以在回调函数里面获取值
-
setState
方法通过一个队列机制实现state
更新,当执行setState
的时候,会将需要更新的state
合并之后放入状态队列,而不会立即更新this.state
- 如果我们不使用
setState
而是使用this.state.key
来修改,将不会触发组件的re-render
。 - 如果将
this.state
赋值给一个新的对象引用,那么其他不在对象上的state
将不会被放入状态队列中,当下次调用setState
并对状态队列进行合并时,直接造成了state
丢失
1.1 setState批量更新的过程
在 react
生命周期和合成事件执行前后都有相应的钩子,分别是 pre
钩子和 post
钩子, pre
钩子会调用 batchedUpdate
方法将 isBatchingUpdates
变量置为 true
,开启批量更新,而 post
钩子会将 isBatchingUpdates
置为 false
-
isBatchingUpdates
变量置为true
,则会走批量更新分支,setState
的更新会被存入队列中,待同步代码执行完后,再执行队列中的state
更新。isBatchingUpdates
为true
,则把当前组件(即调用了setState
的组件)放入dirtyComponents
数组中;否则batchUpdate
所有队列中的更新 - 而在原生事件和异步操作中,不会执行
pre
钩子,或者生命周期的中的异步操作之前执行了pre
钩子,但是pos
钩子也在异步操作之前执行完了,isBatchingUpdates
必定为false
,也就不会进行批量更新
enqueueUpdate
包含了 React
避免重复 render
的逻辑。 mountComponent
和 updateComponent
方法在执行的最开始,会调用到 batchedUpdates
进行批处理更新,此时会将 isBatchingUpdates
设置为 true
,也就是将状态标记为现在正处于更新阶段了。 isBatchingUpdates
为 true
,则把当前组件(即调用了 setState
的组件)放入 dirtyComponents
数组中;否则 batchUpdate
所有队列中的更新
1.2 为什么直接修改this.state无效
- 要知道
setState
本质是通过一个队列机制实现state
更新的。 执行setState
时,会将需要更新的state合并后放入状态队列,而不会立刻更新state
,队列机制可以批量更新state
。 - 如果不通过
setState
而直接修改this.state
,那么这个state
不会放入状态队列中,下次调用setState
时对状态队列进行合并时,会忽略之前直接被修改的state
,这样我们就无法合并了,而且实际也没有把你想要的state
更新上去
1.3 什么是批量更新 Batch Update
在一些 mv*
框架中,,就是将一段时间内对 model
的修改批量更新到 view
的机制。比如那前端比较火的 React
、 vue
( nextTick
机制,视图的更新以及实现)
1.4 setState之后发生的事情
-
setState
操作并不保证是同步的,也可以认为是异步的 -
React
在setState
之后,会经对state
进行diff
,判断是否有改变,然后去diff dom
决定是否要更新UI
。如果这一系列过程立刻发生在每一个setState
之后,就可能会有性能问题 - 在短时间内频繁
setState
。React
会将state
的改变压入栈中,在合适的时机,批量更新state
和视图,达到提高性能的效果
1.5 如何知道state已经被更新
传入回调函数
setState({ index: 1 }}, function(){ console.log(this.state.index); })
在钩子函数中体现
componentDidUpdate(){ console.log(this.state.index); }
二、setState循环调用风险
- 当调用
setState
时,实际上会执行enqueueSetState
方法,并对partialState
以及_pending-StateQueue
更新队列进行合并操作,最终通过enqueueUpdate
执行state
更新 - 而
performUpdateIfNecessary
方法会获取_pendingElement
,_pendingStateQueue
,_pending-ForceUpdate
,并调用receiveComponent
和updateComponent
方法进行组件更新 - 如果在
shouldComponentUpdate
或者componentWillUpdate
方法中调用setState
,此时this._pending-StateQueue != null
,就会造成循环调用,使得浏览器内存占满后崩溃
三、事务
- 事务就是将需要执行的方法使用
wrapper
封装起来,再通过事务提供的perform
方法执行,先执行wrapper
中的initialize
方法,执行完perform
之后,在执行所有的close
方法,一组initialize
及close
方法称为一个wrapper
。 - 那么事务和
setState
方法的不同表现有什么关系,首先我们把4
次setStat
e简单归类,前两次属于一类,因为它们在同一调用栈中执行,setTimeout
中的两次setState
属于另一类 - 在
setState
调用之前,已经处在batchedUpdates
执行的事务中了。那么这次batchedUpdates
方法是谁调用的呢,原来是ReactMount.js
中的_renderNewRootComponent
方法。也就是说,整个将React
组件渲染到DOM
中的过程就是处于一个大的事务中。而在componentDidMount
中调用setState
时,batchingStrategy
的isBatchingUpdates
已经被设为了true
,所以两次setState
的结果没有立即生效 - 再反观
setTimeout
中的两次setState
,因为没有前置的batchedUpdates
调用,所以导致了新的state
马上生效
Recommend
About Joyk
Aggregate valuable and interesting links.
Joyk means Joy of geeK