如下几个问题是咱们在实际开发中常常会遇到的场景,下面用几个简单的示例代码来还原一下。javascript
setState
如今有两个组件java
componentDidMount() { console.log('parent componentDidMount'); } render() { return ( <div> <SetState2></SetState2> <SetState></SetState> </div> ); }
组件内部放入一样的代码,并在Setstate1
中的componentDidMount
中放入一段同步延时代码,打印延时时间:react
componentWillUpdate() { console.log('componentWillUpdate'); } componentDidUpdate() { console.log('componentDidUpdate'); } componentDidMount() { console.log('SetState调用setState'); this.setState({ index: this.state.index + 1 }) console.log('state', this.state.index); console.log('SetState调用setState'); this.setState({ index: this.state.index + 1 }) console.log('state', this.state.index); }
下面是执行结果:git
说明:github
setState
不会当即更新didmount
后,父组件didmount
,而后执行更新setstate
?在setTimeout
中调用setState
(例子和在浏览器原生事件以及接口回调中执行效果相同)浏览器
componentDidMount() { setTimeout(() => { console.log('调用setState'); this.setState({ index: this.state.index + 1 }) console.log('state', this.state.index); console.log('调用setState'); this.setState({ index: this.state.index + 1 }) console.log('state', this.state.index); }, 0); }
执行结果:dom
说明:异步
didmount
后执行setState
同步更新setState
只有一次生效?分别执行如下代码:函数
componentDidMount() { this.setState({ index: this.state.index + 1 }, () => { console.log(this.state.index); }) this.setState({ index: this.state.index + 1 }, () => { console.log(this.state.index); }) }
componentDidMount() { this.setState((preState) => ({ index: preState.index + 1 }), () => { console.log(this.state.index); }) this.setState(preState => ({ index: preState.index + 1 }), () => { console.log(this.state.index); }) }
执行结果:工具
1 1
2 2
说明:
setstate
会被合并成一次state
不会被合并因为源码比较复杂,就不贴在这里了,有兴趣的能够去github
上clone
一份而后按照下面的流程图去走一遍。
图不清楚能够点击查看原图
partialState
:setState
传入的第一个参数,对象或函数_pendingStateQueue
:当前组件等待执行更新的state
队列isBatchingUpdates
:react用于标识当前是否处于批量更新状态,全部组件公用dirtyComponent
:当前全部处于待更新状态的组件队列transcation
:react的事务机制,在被事务调用的方法外包装n个waper
对象,并一次执行:waper.init
、被调用方法、waper.close
FLUSH_BATCHED_UPDATES
:用于执行更新的waper
,只有一个close
方法对照上面流程图的文字说明,大概可分为如下几步:
partialState
参数存储在当前组件实例的state暂存队列中。waper
方法,遍历待更新组件队列依次执行更新。componentWillReceiveProps
。state
进行合并,得到最终要更新的state对象,并将队列置为空。componentShouldUpdate
,根据返回值判断是否要继续更新。componentWillUpdate
。render
。componentDidUpdate
。在react
的生命周期和合成事件中,react
仍然处于他的更新机制中,这时isBranchUpdate
为true。
按照上述过程,这时不管调用多少次setState
,都会不会执行更新,而是将要更新的state
存入_pendingStateQueue
,将要更新的组件存入dirtyComponent
。
当上一次更新机制执行完毕,以生命周期为例,全部组件,即最顶层组件didmount
后会将isBranchUpdate
设置为false。这时将执行以前累积的setState
。
由执行机制看,setState
自己并非异步的,而是若是在调用setState
时,若是react
正处于更新过程,当前更新会被暂存,等上一次更新执行后在执行,这个过程给人一种异步的假象。
在生命周期,根据JS的异步机制,会将异步函数先暂存,等全部同步代码执行完毕后在执行,这时上一次更新过程已经执行完毕,isBranchUpdate
被设置为false,根据上面的流程,这时再调用setState
便可当即执行更新,拿到更新结果。
partialState
合并机制咱们看下流程中_processPendingState
的代码,这个函数是用来合并state
暂存队列的,最后返回一个合并后的state
。
_processPendingState: function (props, context) { var inst = this._instance; var queue = this._pendingStateQueue; var replace = this._pendingReplaceState; this._pendingReplaceState = false; this._pendingStateQueue = null; if (!queue) { return inst.state; } if (replace && queue.length === 1) { return queue[0]; } var nextState = _assign({}, replace ? queue[0] : inst.state); for (var i = replace ? 1 : 0; i < queue.length; i++) { var partial = queue[i]; _assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial); } return nextState; },
咱们只须要关注下面这段代码:
_assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
若是传入的是对象,很明显会被合并成一次:
Object.assign( nextState, {index: state.index+ 1}, {index: state.index+ 1} )
若是传入的是函数,函数的参数preState是前一次合并后的结果,因此计算结果是准确的。
componentDidMount
调用setstate
在componentDidMount()中,你 能够当即调用setState()。它将会触发一次额外的渲染,可是它将在浏览器刷新屏幕以前发生。这保证了在此状况下即便render()将会调用两次,用户也不会看到中间状态。谨慎使用这一模式,由于它常致使性能问题。在大多数状况下,你能够 在constructor()中使用赋值初始状态来代替。然而,有些状况下必须这样,好比像模态框和工具提示框。这时,你须要先测量这些DOM节点,才能渲染依赖尺寸或者位置的某些东西。
以上是官方文档的说明,不推荐直接在componentDidMount
直接调用setState
,由上面的分析:componentDidMount
自己处于一次更新中,咱们又调用了一次setState
,就会在将来再进行一次render
,形成没必要要的性能浪费,大多数状况能够设置初始值来搞定。
固然在componentDidMount
咱们能够调用接口,再回调中去修改state
,这是正确的作法。
当state初始值依赖dom属性时,在componentDidMount
中setState
是没法避免的。
componentWillUpdate
componentDidUpdate
这两个生命周期中不能调用setState
。
由上面的流程图很容易发现,在它们里面调用setState
会形成死循环,致使程序崩溃。
在调用setState
时使用函数传递state
值,在回调函数中获取最新更新后的state
。