在react开发过程中,state是组件一个重要的属性,对state的管理也尤为重要,这里记录一下踩坑经历
修改state正确方式
对于修改组件state正确的方式如下:
// 错误
this.state.title = 'React';
// 正确
this.setState({title: 'React'});
有两个比较重要的点,一是对state的修改是异步,二是对多个相邻的state的修改可能会合并到一起一次执行。代码举例:
异步执行
class Clock extends React.Component {
constructor(props) {
super(props);
this.state = { num: 1 };
}
handleClick() {
this.setState((prevState, props) => ({
num: prevState.num + 1
}));
console.info(this.state.num);
}
render() {
return (
<div>
<h1 onClick={this.handleClick.bind(this)}>Hello, world!</h1>
<h2>It is {this.state.num}.</h2>
</div>
);
}
}
ReactDOM.render(<Clock />, document.getElementById("root"));
结果在ui显示每一次加1之后的结果,而console打印的总是上一次ui的值,也就可以确定state并没有立即执行,而是异步去执行的。我们可以通过添加一个回调函数到第二个参数,让state更新完成之后来执行该回调,代码如下:
handleClick() {
this.setState({
num : this.state.num + 1
});
}
此时ui上显示的数值与console打印的会是一致。另一种情况,如果对state的修改依赖state和prop,那么由于异步的原因就会产生很多异常情况,我们可以可以传入包含两个参数的函数到第一个参数来解决,代码如下:
handleClick() {
this.setState((prevState, props) => ({
num: prevState.num + 1
}),() => console.info(this.state.num));
}
第一个参数prevState表示前一次的state,后一个参数表示当前状态组件的prop值,显示的让该操作同步执行
批量单次执行
对于多次相邻的state修改操作的执行会被合并在一起执行,代码如下:
handleClick() {
this.setState({
num : this.state.num + 1
},() => console.info(this.state.num));
this.setState({
num : this.state.num + 1
},() => console.info(this.state.num));
this.setState({
num : this.state.num + 1
},() => console.info(this.state.num));
}
最终数值每次只会加1,我们可以看作是以下代码的执行:
Object.assign(
previousState,
{num : this.state.num + 1},
{num : this.state.num + 1}
)
因此,被合并之后最终只会保留一个更新。解决方法和上述解决异步的方式类似:
handleClick() {
this.setState((prevState,props) => ({
num : prevState.num + 1
}),() => console.info(this.state.num));
this.setState((prevState,props) => ({
num : prevState.num + 1
}),() => console.info(this.state.num));
this.setState((prevState,props) => ({
num : prevState.num + 1
}),() => console.info(this.state.num));
}
结果是我们期望的每次加3,但是需要注意的是,console打印的只会是每次加3之后的数值,并不会是每次加1的结果
State与Immutable
React官方建议把State当作是不可变对象,一方面是如果直接修改this.state,组件并不会重新render;另一方面State中包含的所有状态都应该是不可变对象。当State中的某个状态发生变化,我们应该重新创建这个状态对象,而不是直接修改原来的状态。那么,当状态发生变化时,如何创建新的状态呢?根据状态的类型,可以分成三种情况:
状态的类型是不可变类型(数字,字符串,布尔值,null, undefined)
这种情况最简单,因为状态是不可变类型,直接给要修改的状态赋一个新值即可。如要修改count(数字类型)、title(字符串类型)、success(布尔类型)三个状态:this.setState({ count: 1, title: 'Redux', success: true })
状态的类型是数组
如有一个数组类型的状态books,当向books中增加一本书时,使用数组的concat方法或ES6的数组扩展语法(spread syntax):// 方法一:将state先赋值给另外的变量,然后使用concat创建新数组 var books = this.state.books; this.setState({ books: books.concat(['React Guide']); }) // 方法二:使用preState、concat创建新数组 this.setState(preState => ({ books: preState.books.concat(['React Guide']); })) // 方法三:ES6 spread syntax this.setState(preState => ({ books: [...preState.books, 'React Guide']; }))
当从books中截取部分元素作为新状态时,使用数组的slice方法:
// 方法一:将state先赋值给另外的变量,然后使用slice创建新数组 var books = this.state.books; this.setState({ books: books.slice(1,3); }) // 方法二:使用preState、slice创建新数组 this.setState(preState => ({ books: preState.books.slice(1,3); }))
注意不要使用push、pop、shift、unshift、splice等方法修改数组类型的状态,因为这些方法都是在原数组的基础上修改,而concat、slice、filter会返回一个新的数组。
状态的类型是普通对象(不包含字符串、数组)
3.1 使用ES6 的Object.assgin方法// 方法一:将state先赋值给另外的变量,然后使用Object.assign创建新对象 var owner = this.state.owner; this.setState({ owner: Object.assign({}, owner, {name: 'Jason'}); }) // 方法二:使用preState、Object.assign创建新对象 this.setState(preState => ({ owner: Object.assign({}, preState.owner, {name: 'Jason'}); }))
3.2 使用对象扩展语法
// 方法一:将state先赋值给另外的变量,然后使用对象扩展语法创建新对象 var owner = this.state.owner; this.setState({ owner: {...owner, name: 'Jason'}; }) // 方法二:使用preState、对象扩展语法创建新对象 this.setState(preState => ({ owner: {...preState.owner, name: 'Jason'}; }))
参考:
https://reactjs.org/docs/state-and-lifecycle.html
https://reactjs.org/docs/state-and-lifecycle.html