解密setState
React是通过管理状态来实现对组件的管理。react通过this.state来访问state,通过this.setState()方法来更新state。当this.setState()被调用的时候,react会重新调用render方法来重新渲染UI。
setState异步更新
setState通过一个队列机制实现state更新。当执行setState时,会将需要更新的state合并后放入队列,而不是直接更新this.state。队列机制可以高效的批量更新state,如果不是通过setState而是直接修改this.state,那么state是不会被放入状态队列,当下次调用setState并对状态队列合并时,将会忽略之前被直接修改的state。相关代码如下:
//将新的state合并到状态队列中
var nextState = this._propcessPendingState(nextProps, nextContext);
//根据更新队列和shouldComponentUpdate的状态来判断是否需要更新组件
var shouldUpdate =
this._pendingForceUpdate ||
!inst.shouldComponentUpdate ||
inst.shouldComponentUpdate(nextProps, nextState, nextContext)
setState循环调用风险
当调用setState时,实际上会执行enqueueSetState方法,并对partialState以及_pendingStateQueue更新队列进行合并操作,最终通过enqueueUpdate执行state更新。
而performUpdateIfNecessary方法会获取_pendingElement、_pendingStateQueue、_pendingForceUpdate。并调用receiveComponent和updateComponent方法进行组件更新。
如果在shouldComponentUpdate或componentWillUpdate方法中调用setState,此时this._pendingStateQueue != null, 则performUpdateIfNecessary方法就会调用updateComponent方法进行组件更新,但updateCompnent又会调用shouldComponentUpdate和componentWullUpdate方法,因此造成循环调用,使得浏览器内存占满后崩溃。
看下setState的源码:
//更新state
ReactComponent.prototype.setState = function(partialState, callback) {
this.updater.enqueueSetState(this, partialState);
if(callback) {
this.updater.enqueueCallback(this, callback, 'setState')
}
}
enqueueSetState: function(publicInstace, partialState) {
var internalInstance = getInternalInstanceReadyForUpdate(
publicInstance,
'setState'
)
if(!internalInstance) {
return
}
//更新队列合并操作
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = [])
queue.push(partialState)
enqueueUpdate(internalInstance)
//如果存在_pendingElement, _pengdingStateQueue和_pendingForceUpdate,则更新组件
performUpdateIfNecessary: function(transaction) {
if(this._pendingElement != null) {
ReactReconciler.receiveComponent(this, this._pendingElement, transaction, this._context);
}
if(this._pendingStateQueue !== null || this._pendingForceUpdate) {
this.updateComponent(transaction, this._currentElement, this._currentElement, this._context, this._context)
}
}
}
setState调用栈
既然setState最终是通过enqueueUpdate执行state更新,那么enqueueUpdate如何更新state的呢?
先看如下例子:
import React, { Component } from "react"
class Comp extends Component {
constructor(props) {
super(props)
this.state = {
val: 0
}
}
componentDidMount() {
this.setState({val: this.state.val+1})
console.log(this.state.val)
this.setState({val: this.state.val+1})
console.log(this.state.val)
setTimeout(()=>{
this.setState({val: this.state.val+1})
console.log(this.state.val)
this.setState({val: this.state.val+1})
console.log(this.state.val)
})
}
render() {
return null
}
}
上面代码打印出来的分别是0、0、2、3.
如果答案和你心中的不一样,那么enqueueUpdate到底做了什么?
enqueueUpdate源码:
function enqueueUpdate(component) {
esureInjected()
//如果不在批量更新模式
if(!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enequeueUpdate, component)
return;
}
//如果处于批量更新模式,则将该组件保存在dirtyComponent中
dirtyComponents.push(component)
}
如果isBatchingUpdates为true,则对所有队列中的更新执行batchedUpdates方法,否则只把当前组件(即调用了setState的组件)放入dirtyComponents数组中。
那么batchingStrategy究竟是什么,其实他是一个简单的对象,定义了一个isBatchingUpdates的布尔值,以及batchedUpdates方法。
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdate: function(callback, a, b, c, d, e) {
if(alreadyBatchingUpdates) {
callback(a,b,c,d,e)
} else {
transaction.perform(callback, null, a,b,c,d,e)
}
}
}
该方法中有一个transaction.perform,涉及到事务的概念。
初识事务
事务就是将需要执行的方法使用wrapper封装起来,再通过事务提供的perform执行。而在perform之前,先执行所有wrapper中的initialize方法,执行完perform之后(即执行完method方法后)再执行所有的close方法。一组initialize以及close方法被称为一个wrapper。从图中可以看到事务支持多个wrapper叠加。
到实现上,事务提供了一个mixin方法供其他模块实现自己需要的事务,而要使用事务的模块,除了需要把mixin混入自己的事务实现中外,还要额外实现一个抽象的getTransactionWrappers接口。这个接口用来获取所有需要封装的前置方法(initialize)和收尾方法(close)。因此它需要返回一个数组对象。每个对象分为有key为initialize和close方法。下面是一个简单使用事务的例子:
var Transaction = require('./Transaction')
//我们自己定义的事务
var MyTransaction = function() {
//...
}
Object.assign(MyTransacton.prototype, Transaction.Mixin, {
getTransactionWrappers: function() {
return [{
initialize: function() {
console.log('before method perform')
},
close: function() {
console.log('after method perform')
}
}]
}
})
var transaction = new MyTransaction()
var testMethod = function() {
console.log('test')
}
transaction.perform(testMethod)
//before method perform
//test
//after method perform
解密
那么事务到底是如何导致前面所述的setState的各种不同表现呢?
首先,我们要了解事务跟setState的不同表现有什么关系?4次setState简单归类,前两次属于一类,因为他们在同一次调用栈执行,setTimeout中的2次setState属于另外一类。
如图,在componentDidMount中直接调用的两次setState比setTimeout中调用的两次setState,其调用栈简单的多。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。