this.setState( )方法是React.js中最多见的一种方法,利用它能够控制各类状态变化,达到页面各类交互效果,可是,咱们在React开发中偶尔会发现,明明已经经过this.setState( )方法处理过某个state的值,可是在后续的方法里,log打印出来仍然是以前的值,或者,第一次获取到原来的值,第二次才能获取到设置以后的新值,让人误觉得是由于电脑或浏览器性能问题形成的"延迟"问题。react
为了理解这个问题,咱们首先来看一下setState这个过程当中发生了什么:浏览器
setState
传入的partialState
参数存储在当前组件实例的state
暂存队列中。componentWillReceiveProps
。state
暂存队列中的state
进行合并,得到最终要更新的state
对象,并将队列置为空。componentShouldUpdate
,根据返回值判断是否要继续更新。componentWillUpdate
。render
从新渲染。componentDidUpdate
。首先思考为何会出现这种状况,在facebook给出的官方文档中咱们能够看到这么一段话:性能优化
setState(updater[, callback])
Think of setState( ) as a request rather than an immediate command to update the component. For better perceived performance, React may delay it, and then update several components in a single pass. React does not guarantee that the state changes are applied immediately.app
setState( ) does not always immediately update the component. It may batch or defer the update until later. This makes reading this.state right after calling setState( ) a potential pitfall. Instead, use componentDidUpdate or a setState callback (setState(updater, callback)), either of which are guaranteed to fire after the update has been applied. If you need to set the state based on the previous state, read about the updater argument below.dom
总结如下,就是如下几点:异步
举个简单例子:函数
constructor(props) { super(props); this.state = { num: 1 }; } componentDidMount = () => { this.setState({ num: this.state.num + 1 }); console.log(this.state.num); // 1 }
这是由于this.setState( )自己是异步的,程序异步运行,能够提升程序运行的效率,没必要等一个程序跑完,再跑下一个程序,特别当这两个程序是无关的时候。React会去合并全部的state变化,在前一个方法未执行完时,就先开始运行后一个方法。可是实际操做中,为了能实时获取后一个状态值,须要一些解决的办法。性能
尝试一下换个写法,利用全局属性的办法而不是用state的方式去获取数据:优化
constructor(props) { super(props); this.num = 1; } componentDidMount = () => { this.num = this.num + 1; console.log(this.num); // 2 }
这实际上是一种取巧的方式,写法方便,原理简单,可是并不十分推荐,由于它并不符合React中关于有状态组件的设计理念,存在有可能没法触发刷新的风险(虽然在个人开发过程从没有发生这样的事),因此仍是但愿你们优先使用下面的方法。this
回调函数众所周知,就是某个函数执行完毕后执行的函数,利用它能够确保在this.setState( )整个函数执行完成以后去获取this.state.xxx的值:
constructor(props) { super(props); this.state = { num: 1 }; } componentDidMount = () => { this.setState({ num: this.state.num + 1 }, () => { console.log(this.state.num); // 2 }); console.log(this.state.num); // 1 }
控制台按顺序前后打印出两个结果:
1 2
首先简单回顾一下,利用setTimeout( )模拟一下前文提到的Javascript中的异步:
foo = () => { console.log('11111111'); setTimeout(function(){ console.log('22222222'); },1000); }; bar = () => { console.log('33333333'); } foo(); bar(); // 11111111 // 33333333 // 22222222
因此,在上述代码块中,在前一方法(foo)执行时,后一方法(bar)也能够执行。符合异步的基本概念,程序并不按顺序执行。在foo函数中执行到setTimeout的时候,函数会跳出,并先执行bar( )方法,这样就模拟了一个异步的效果。这里顺便再提一下前面说的,setState方法经过一个队列机制实现state更新,当执行setState的时候,会将须要更新的state合并以后放入状态队列,而不会当即更新,经过下面的例子可见。
constructor(props) { super(props); this.state = { num: 1, }; } componentWillMount = () => { this.setState({ num: this.state.num + 1, }); console.log(this.state.num); this.setState({ num: this.state.num + 1, }); console.log(this.state.num); } render() { console.log(this.state.num); return (<div />); }
代码输出结果为 1,1,2
利用setTimeout方法能够解决state的异步问题,由于setState只在合成事件和钩子函数中是“异步”的,在原生事件和setTimeout 中都是同步的:
componentWillMount = () => { setTimeout(() => { this.setState({ num: this.state.num + 1, }); console.log(this.state.num); // 1 this.setState({ num: this.state.num + 1, }); console.log(this.state.num); // 2 }, 0); }
根据前面文档所说,在componentDidUpdate( )方法中去获取新的state值,根据React的生命周期,此时this.state已经更新。
constructor(props) { super(props); this.state = { num: 1 }; } componentWillMount = () => { this.setState({ num: this.state.num + 1 }); } componentDidUpdate = () => { console.log(this.state.num); // 2 }
⚠️注意,不少新人在遇到这种问题时无所适从,可能会用一些投机取巧的方式,方面的全局对象是一种方式,还有一种就是绕过setState直接赋值:
this.state.num = 2 // 2
理论上讲,这种方法固然也能达到赋值目的,但将state设计成更新延缓到最后批量合并再去渲染,对于应用的性能优化是有极大好处的,若是每次的状态改变都去从新渲染真实dom,那么它将带来巨大的性能消耗,因此不建议上面写法。
⚠️若是在shouldComponentUpdate或者componentWillUpdate方法中调用setState,此时this._pending-StateQueue != null,就会形成循环调用,使得浏览器内存占满后崩溃。