组件state
必须能表明一个组件UI呈现的完整状态集,即组件的任何UI改变均可以从state
的变化中反映出来;同时,state
还必须表明一个组件UI呈现的最小状态集,即state
中的全部状态都用于反映组件UI的变化,没有任何多余的状态,也不该该存在经过其余状态计算而来的中间状态。
首先,什么是普通属性?数组
咱们的组件都是使用ES6
的class
定义的,因此组件的属性其实也就是class
的属性(更确切的说法是class
实例化对象的属性,但由于JavaScript本质上是没有类的定义的,class
只不过是ES6
提供的语法糖,因此这里模糊化类和对象的区别)。异步
在ES6
中,可使用this.{属性名}
定义一个class
的属性,也能够说属性是直接挂载到this
下的变量。所以,state
、props
实际上也是组件的属性,只不过它们是React
为咱们在Component class
中预约义好的属性。除了state
、props
之外的其余组件属性称为组件的普通属性。函数
好比,组件中须要一个定时器来自动更新显示时间时,咱们都会须要一个timerId
来保存该定时器,已在须要的可以清除它。这就是一个普通的属性,由于它和组件要渲染的内容没有直接关系。性能
所以,当咱们在组件中须要用到一个变量,而且它与组件的渲染无关时,就应该把这个变量定义为组件的普通属性,直接挂载到this
下,而不是做为组件的state
。或者更直观的判断是,看组件render方法中是否使用到了这个变量,若是没有,它就是一个普通属性
。优化
state
和props
又有什么区别呢?this
state
和props
都直接和组件的UI渲染有关,它们的变化都会触发组件从新渲染,但props
对于使用它的组件来讲是只读的,是经过父组件传递过来的,要想修改props
,只能在父组件中修改;而state
是组件内部本身维护的状态,是可变的。调试
其实区分state
和props
的关键就是,‘控制权’是在组件自身,仍是由其父组件来控制的。code
(1)这个变量是否经过props
从父组件中获取?若是是,那么它不是一个状态。对象
(2)这个变量是否在组件的整个生命周期中都保持不变?若是是,那么它不是一个状态。生命周期
(3)这个变量是否能够经过其余状态(state)
或者属性(props)
计算获得?若是是,那么它不是一个状态。
(4)这个变量是否在组件的render
方法中使用?若是不是,那么它不是一个状态。这种状况下,这个变量更适合定义为组件的一个普通属性。
一、state不能直接进行修改
直接赋值形式修改state,不会触发组件的render。修改state
须要经过setState
方法进行修改。
// 错误 this.state.title = 'React'; // 正确 this.setState({ title: 'React' })
二、state的更新是异步的
调用setState
时,组件的state
并不会当即改变,setState
只是把要修改的状态放入一个队列中,React
会优化真正的执行时机,而且出于性能缘由,可能会将屡次setState
的状态修改合并成一次状态修改。
因此不要依赖当前的state
,计算下一个state
。当真正执行状态修改时,依赖的this.state
并不能保证是最新的state
,由于React
会把屡次state
的修改合并成一次,这时this.state
仍是这几回state
修改前的state
。
另外,须要注意的是,一样不能依赖当前的props
计算下一个状态,由于props
的更新也是异步的。
如,电商类购物车添加购买数量时,若是连续点击两次,就会连续调用两次this.setState({count: this.state.count + 1})
,在React
合并屡次修改成一次的状况下,至关于等价执行了下面的操做:
Object.assign( previousState, {count: this.state.count + 1}, {count: this.state.count + 1} )
因而,最终只会增长一次操做的值。
若是有这样的需求,可使用另外一个接收一个函数做为参数的setState
,这个函数有两个参数,第一个是当前最新状态(本次组件状态修改生效后的状态)的前一个状态preState
(本次组件状态修改前的状态),第二个参数是当前最新的属性props
。代码以下:
this.setState((preState, props) => ({ count: preState.count + 1; }))
三、state更新是一个合并的过程
咱们在每次调用setState
修改state时,并非须要把全部的值都进行修改,而只是设置咱们要修改的部分值。React
会合并新值导员的组件state
中,同时保留没有发生变化的原来的state
的值。
React
官方建议把state
看成不可变对象,一方面,直接修改this.state
,组件并不会从新render
;另外一方面,state
中包含的全部状态都应该是不可变对象。当state
中的某个状态发生变化时,应该从新建立这个状态对象,而不是直接修改原来的状态。
能够分为下面三种状况:
1.状态的类型是不可变类型(数字、字符串、布尔值、null、undefined)
this.setState({ count: 1, // 数字类型 title: 'React', // 字符串类型 success: true // 布尔类型 })
2.状态的类型是数组
// 方法一:使用preState、concat建立新数组 this.setState(preState => ({ books: preState.books.concat(['React']); })) // 方法二:ES6 spread syntax this.setState(preState => ({ books: [...preState.books, 'React']; }))
当从数组中截取部分元素做为新的状态时,可以使用数组的slice
方法:
this.setState(preState => ({ books: preState.books.slice(1,3); }))
当从数组中过滤部分元素做为新的状态时,可以使用数组的filter
方法:
this.setState(preState => ({ books: preState.books.filter(item => { return item !== 'React'; }); }))
注意,不要使用push、pop、shift、unshift、splice
等方法修改数组类型的状态,由于这些方法都是在原数组的基础上修改的,而concat、slice、filter
会返回一个新的数组。
3.状态的类型是普通对象(不包含字符串、数组)
(1)使用ES6
的Object.assgin
方法:
this.setState(preState => ({ owner: Object.assign({}, preState.owner, {name: 'Tom'}); }))
(2)使用对象扩展语法(object spread properties):
this.setState(preState => ({ owner: {...preState.owner, name: 'Tom'}; }))
总结一下,建立新的状态对象的关键是,避免使用会直接修改原对象的方法,而是使用能够返回一个新对象的方法。固然,也可使用一些Immutable
的JS库(如Immutable.js
)实现相似的效果。
为何React
推荐组件的状态是不可变对象呢?一方面是由于对不可变对象的修改会返回一个新对象,不须要担忧原有对象在不当心的状况下被修改致使的错误,方便程序的管理和调试;另外一方面是出于性能考虑,当对象组件状态都是不可变对象时,在组件的shouldComponentUpdate
方法中仅须要比较先后两次状态对象的引用就能够判断状态是否真的改变,从而避免没必要要的render
调用。