一,在react中可以通过this.state.{属性}的方式直接获取state,但当我们想要修改state的时候有许多的坑需要注意,以下 为三种常见的陷阱:
不能直接修改state。
组件直接修改state,并不会重新触发render。列如:
this.state.title='React';
正确修改方式是:
this.setState({title:'React'});
state的更新是异步的
调用setState时,组件state并不会立即改变,而是把要修改的状态放入事件队列中,而react优化了真正的执行时机,并且出于本身性能原因,可能会将多次setState的状态修改合并成一次状态修改。因此不要依靠当前的state计算下一个state,因为当真正执行状态修改时,有时候依赖的this.state并不能保证是最新的state,因为react本身会把多次state合并成一次,这时this.state还是旧state,因此也不能依赖当前的props计算下一个状态,因为props的更新也是异步。如:对于react常用的列子中有一个点击加号数值增加一的操作,点击一次+,数量会加1,如果连续点击两次,还是会加1,这是在react合并修改为一次的情况下,相当与执行了如下代码:
Object.assign(
previousState,
{quantity:this.state.quantity+1},
{quantity:this.state.quantity+1},
)
于是后面覆盖前面的操作,最终数值只加1,此时可以使用另一个函数作为参数的setState,这个函数有两个参数,第一个参数是本次组件修改前的状态,第二个参数是当前最新的props。
正确修改方式是:
this.setState((preState, props)=>counter:preState.quantity+1)
state的更新是一个合并的过程
当调用ssetState修改组件的状态时,只需要传入发生改变的state,而不是完整的state,因为组件state的更新时一个合并的过程,列如,一个组件的状态为:
this.state={
title:'React',
content:'React is an wondeful JS library'
}
注:当只需要修改title时,将修改的title传给setState即可:
this.setState({title:'ReactJs'});
react会合并最新的title到原来的状态,同时保留原来状态的content,最终合并state为:
this.state={
title:'ReactJs,
content:''React is an wondeful Js library
}
二,state与不可变对象
react官方把state当成不可变对象,一方面直接修改this.state,组件并不会重新render;另一方面,state中包含的所有状态都应该是不可变的对象,当state当中的某一个状态发生变化时,应该重新创建这个状态对象,而不是直接修改原来的state状态,那么当状态发生变化时,如何去创建新的状态呢,我们根据状态类型可以分为下面三种情况:
状态类型为不可变类型(number,string,bool,bull,undefined)
这种情况最简单,因为状态是不可变类型,所以直接给要修改的状态赋一个新值即可,列如下面我们要修改的count为number型,title(string),success(bool)三个状态:
this.setState({
count:1,
title:'React',
success:true
})
状态类型为数组
假如有一个数组类型的状态books,当想books中增加一本书时,既可使用数组的concat方法或者es6的扩展语法(apread syntax)
方法一:使用preState,concat创建新数组
this.setState((preState)=>books:preState.books.concat(['React Guide']))
方法二:ES6 spread syntax
this.setState(preState=>books:[...preState,''React Guide])
当我们从books中截取部分元素作为新状态时,可以用数组的slice方法:
this.setState(preState=>books:preState.books.slice(1,3))
当从books中过滤部分元素后,作为新状态时,可以使用filter方法:
this.setState(preState => {
books: preState.books.filter(item => {
return item != 'React';
})
})
注意:不要使用push,pop,shift,unshift,splice登方法修改数组类型的状态,因为这些方法都是在原数组的基础上修改的,而concat,slice,filter会返回一个新的数组。
方法三:状态的类型是普通对象(不包含:string,array)
使用es6的Object.assgin()方法
this.setState({
onwer:Object.assgin({},preState.onwer,{name:'Jason'});
})
使用对象扩展语法(Object spread properties):
this.setState(preState=>{
owner:{...preState.owner,name:'Jason'}
})
总结:
创建新的状态的关键是,避免使用会直接修改原对象的方法而是使用可以返回一个新对象的方法,当然可以使用Immutable的JS库(Immutable.js)实现类似的效果。
思考:
为什么React推荐组件状态的修改时不可变对象呢?
- 不可变对象的修改会返回一个新的对象,不用担心原对象在不小心的情况下修改导致的错误,方便程序的管理和调试
- 处于性能的考虑,对象组件的状态是不可变对象时,在组件的shouldComponentUpdate方法中仅需要比较前后两次状态对象的引用就可以判断状态是否真的改变,从而避免不必要的render调用
三:除了以上方法修改react组件的状态之外,我们还经常会用到replaceState改变组件的状态
replaceState()方法与setState()类似,但是方法只会保留nextState中状态,原state不在nextState中的状态都会被删除。使用语法:
replaceState(object nextState,[, function callback])
nextState,将要设置的新状态,该状态会替换当前的state。
callback,可选参数,回调函数。该函数会在replaceState设置成功,且组件重新渲染后调用。
如:
class App extends React.Component{
constructor(props);
this.state={
count:1
title:'数字计算'
}
}
handleClick=()=>{
this.replaceState({
count:this.state.count+1
})
}
render(){
return(
<button onClick={this.onClick}>点我</button>
)
}
}
结果为:
{
count:1
}