一、setState使用
接触react
框架不久,却在项目当中发现,非受控组件其更新时机的触发方式——setState
,是一个异步的过程
下面是一个例子:
handelTabChange (tabName) {
this.setState({
tab: tabName
})
this.updateTabPane()
}
updateTabPane () {
const { tab } = this.state
console.log( tab ) // not the latest one
}
此时tab
发生变化的时候,输出的却依然是上一个tab
的名称,因此可以判断updateTabPane
是在setState
之前执行了。
那么,为什么setState
需要异步去改变组件的state
呢?
React
组件是靠单向数据流构建页面dom的,除开props
,自身的state
改变也是引起组件渲染的主要因素,为了节省性能消耗,react
有一套自己的state
更新策略,为的是减少state
更新对页面渲染的消耗,而这套更新策略的入口就是setState
方法。
这同样也解释了为什么直接对this.state
进行赋值操作并不能改变页面的渲染结果。
那么在异步的过程中,setState
究竟做了哪些事儿呢?
二、 setState更新组件state
总体归纳一下setState
更新组件的流程,调用setState
后,会把我们想要更新的state
压进一个待更新队列(即内部实例的_pendingStateQueue
),然后执行入栈更新操作enqueueUpdate
,判定是否处于批量更新状态。如果正在进行组件的批量更新,那么久先把实例推进dirtyComponents
里面等待下一次批量更新;相反若没有批量更新在执行,则会调用一个批量更新的事务。
/**
* setState源码
**/
ReactComponent.prototype.setState = function (partialState, callback) {
this.updater.enqueueSetState(this, partialState); // 传入新state进队列
if (callback) { // 推入callback队列
this.updater.enqueueCallback(this, callback, 'setState')
}
}
enqueueSetState: function(publicInstance, partialState) {
// 获取内部实例
var internalInstance = getInternalInstanceReadyForUpdate(publicInstance, 'setState')
if (!internalInstance) {
return
}
// 更新队列合并操作
var queue = internalInstance._pendingStateQueue || (internalInstance._pendingStateQueue = [])
queue.push(partialState)
// 更新代码
enqueueUpdate(internalInstance)
}
setState
把我们希望更新的partialState
推入待更新队列之后,就撒手交给enqueueUpdate
去处理更新的时机了,我们看一下enqueueUpdate
又为我们做了什么
/**
* enqueueUpdate源码
**/
function enqueueUpdate(component) {
ensureInjected()
// 如果不处于批量更新模式
if (!batchingStrategy.isBatchingUpdates) {
batchingStrategy.batchedUpdates(enqueueUpdate, component)
return
}
// 如果处于批量更新模式,则将该组件保存在 dirtyComponents 中
dirtyComponents.push(component)
}
可以看到enqueueUpdate
当中出现了一个重要的对象batchingStrategy
,他有一个属性isBatchingUpdates
用来告诉enqueueUpdate
是应该更新,还是应该等待,把组件推入dirtyComponents
里。可以想象这是一个react
内部,用于控制批量更新的对象,让我们更近距离的了解它
/**
* batchingStrategy源码
**/
var ReactDefaultBatchingStrategy = {
isBatchingUpdates: false,
batchedUpdate: function(callback, a, b, c, d, e) {
var alreadyBatchingStrategy = ReactDefaultBatchingStrategy. isBatchingUpdates
ReactDefaultBatchingStrategy. isBatchingUpdates = true
if (alreadyBatchingStrategy) {
callback(a, b, c, d, e)
} else {
transaction.perform(callback, null, a, b, c, d, e)
}
}
}
dirtyComponents
当中提供的batchedUpdates
其实就是我们一直寻找的,真实用来更新我们组件的方法。然而走到这一步,react
却又向我们抛出了一个重大的概念——事务。batchedUpdates
当中transaction.perform
就是事务的调用
三、 事务与componentDidMount
了解了setState
执行的全过程,我们也清楚了这个函数其实并不一定是异步去执行的,倘若没有在进行更新dom时,它还是会立即触发dom的更新
//this.state.val = 0
setTimeout(() => {
this.setState({val: this.state})
console.log(this.state.val) // 1
this.setState({val: this.state})
console.log(this.state.val) // 2
}, 0)
当他异步的时候,从setState
的源码中我们也看到了,如果想要在新的state
更新后触发操作,只需要在setState
的第二个参数当中传入你想要执行的回调即可。
但是,如果想要解释以下现象,我们还需要向大家介绍事务的概念。
componentDidMount () {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 0
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 0
}
按照刚才的想法,当componentDidMount
执行的时候,按理说页面上已经有完整的dom渲染结束了,为什么此时我调用setState
不能像setTimeout
里一样,立即执行对state
的更新呢?下面先抛出事务的简介。
简单来说,事务是一种react
的处理机制,通过使用wrapper
包裹你实际想要调用的方法,做一些前置(initialize
)和收尾(close
)的工作,所以在事务包裹的方法内,会优先触发前置钩子,以及执行完后会有收尾方法调用,这在react
用作异常处理使用。
所以此时不难想到,其实componentDidMount
是react
挂载dom节点的事务的收尾工作,在这个环节操作state
会被阻塞,直到事务完全执行完毕后,才会重新调用更新。
四、 setState与生命周期
setState
会触发组件的更新,同时在组件生命周期的钩子函数中我们往往会有对state
的操作,操作不当很有可能发生state change
=》 update
=》 change state
=》 state change
……的死循环,那么哪些钩子函数内使用setState是安全的呢。
我们把生命周期钩子函数罗列出来
constructor -> componentWillMount -> render -> componentDidMount ->
componentWillReceiveProps -> shouldComponentUpdate -> componentWillUpdate -> render -> componentDidUpdate
当中constructor
中本身就有state
的声明,在这里是最初的state
创建,因此不需要使用setState
componentWillMount
中,如果进行同步setState
调用,此时的操作其实和constructor
内定义state
是一样的,并不会触发组件的额外渲染,当然这里可以做异步的setState
操作,获取页面的初始数据。
render
、shouldComponentUpdate
、componentWillUpdate
这三个函数中,组件还没有渲染结束就继续调用setState
,会无限触发更新,陷入死循环,是要注意的。
因此我们可用setState
的生命周期钩子函数有:componentWillMount
、componentDidMount
、componentWillReceiveProps
、componentDidUpdate
至此setState的原理和使用就介绍完了,但是真正使用的契机却往往是前端开发者需要去琢磨的,对于非控制组件,这是react中必要掌握的技术基础了