接触react后,在项目开发过程中,发现setState的是异步方法,须要在回调函数中才能得到真正的值。javascript
this.setState({
num:this.state.num + 1
},()=>{
console.log(this.state.num);
});复制代码
react为何会这样作呢?setState真的是异步的吗?
查阅相关文章后,才得知,并不是如此。另外两种setState同步的作法。前端
//方法一:在this.setState以后去componentDidUpdate函数中调用,此时的this.state已经更新
componentDidUpdate(){
console.log(this.state.num)
}
//方法二:在setTimeout函数中,在this.setState以后this.state是当即更新的,因此也能够获取到更新后的数据。
setTimeout(()=>{
this.setState({
num:this.state.num + 1
});
console.log(this.state.num);
})复制代码
看到这里,相信不少人都颠覆了以前对setState的异步理解,setState究竟是如何进行组件state更新的呢?
调用setState
后,会把咱们想要更新的state
压进一个待更新队列(即内部实例的_pendingStateQueue
),而后执行入栈更新操做enqueueUpdate
,断定是否处于批量更新状态。若是正在进行组件的批量更新,那么久先把实例推动dirtyComponents
里面等待下一次批量更新;相反若没有批量更新在执行,则会调用一个批量更新的事务。
java
/** * 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
又为咱们作了什么?
react
/** * 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
就是事务的调用。
app
了解了setState
执行的全过程,咱们也清楚了这个函数其实并不必定是异步去执行的
,假若没有在进行更新dom时,它仍是会当即触发dom的更新。
dom
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
会被阻塞,直到事务彻底执行完毕后,才会从新调用更新。ui
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(react16 已废弃,直接使用getDerivedStateFromProps获取props并返回新的state)
、componentDidUpdate
至此setState的原理和使用就介绍完了,可是真正使用的契机却每每是前端开发者须要去琢磨的,对于非控制组件,这是react中必要掌握的技术基础了