因为 setState
在react
中非常重要,所以单独拎出来整理它的要点
什么是setState?
react
是通过在constructor
构造函数中的this.state
去定义状态的,然后是通过this.setState
去修改状态,那么setState
有什么特点呢?
不可变值
state
中数据不可直接使用this.state.xxx = xxx
形式来改变状态,这是因为react
的immutable
概念决定的
- 针对基础类型
直接修改this.state
值类型时,会报警告:Do not mutate state directly. Use setState()
this.setState({
// 使用this.state.count++,会报警告,因为这句直接修改了原count的值:
// Do not mutate state directly. Use setState()
// count: this.state.count++
count: this.state.count + 1,
})
- 针对数组
虽然使用this.state.list.push(xx)
这种方式修改数组后,再使用setState
赋给state
不会报错,但还是不建议这么使用,我们应该遵循immutable
理念,通过拷贝后的数组,再赋值给state
的
// 建议使用这种,对原数据进行拷贝,在拷贝上修改
const newList = this.state.list.slice()
newList.push(3)
this.setState({
list: newList
})
- 针对对象
使用Object.assign,或者使用扩展运算符生成一个新对象方式
this.setState({
obj1: Object.assign({}, this.state.obj1, {x: 1}),
obj2: {...this.state.obj2, x: 1}
})
import React, { Component } from 'react'
class SetStateDemo extends Component{
constructor(props) {
super(props)
this.state = {
count: 0,
list: [
{id: 1, age: 1},
{id: 2, age: 2}
],
obj1: {
a: 1
}
}
}
addCount = () => {
// 建议使用这种,对原数据进行拷贝,在拷贝上修改
const newList = this.state.list.slice()
newList.push(3)
this.setState({
// 使用this.state.count++,会报警告,因为这句直接修改了原count的值:
// Do not mutate state directly. Use setState()
// count: this.state.count++
count: this.state.count + 1,
list: newList,
obj1: {...this.state.obj1, x: 1}
})
console.log(this.state.count) // 这里是异步的
}
render() {
return (
{this.state.count}
)
}
}
export default SetStateDemo;
setState是同步的吗
setState
有时是同步的,有时是异步的,不能一概而论,要区分使用情况来分析:
异步的setState
:
直接使用onXxxx
绑定的事件,使用的setState
是异步的,如果要实时获取数据,需要在setState
第二个参数回调函数中获取(类似vue
中的nextTick
作用)
constructor(props) {
super(props)
this.state = {
count: 0,
list: [
{id: 1, age: 1},
{id: 2, age: 2}
]
}
}
addCount = () => {
this.setState({
count: this.state.count + 1,
}, () => {
console.log('在回调中获取异步后的结果:', this.state.count)
})
console.log(this.state.count) // 这里是异步的
}
同步的setState
:
- 在
setTimeout
中是同步的
render() {
return (
)
}
addCount2 = () => {
setTimeout(() => {
this.setState({
count: this.state.count + 1
})
console.log('setTimeout中的setState:', this.state.count)
})
}
- 在自定义DOM事件中,是同步的
在componentDidMount
生命周期中增加自定义事件,发现执行的setState
是同步的
注意:自定义事件要及时在componentWillUnmount
生命周期解绑,不然会造成内存泄露
class SetStateDemo extends Component{
constructor(props) {
super(props)
this.state = {
count: 0,
}
}
componentDidMount() {
// 自定义事件
document.body.addEventListener('click', this.addCount3)
}
addCount3 = () => {
this.setState({
count: this.state.count + 1
})
console.log('自定义事件中的setState:', this.state.count)
}
componentWillUnmount() {
// 自定义事件要及时解绑,否则会造成内存泄露
document.body.removeEventListener('click', this.addCount3)
}
render() {
return (
{this.state.count}
)
}
setState是否会被合并
- 当使用对象来修改状态时,会被合并
因为在merge
方法中,this.setState
是异步执行的,当执行完this.setState({ count: this.state.count + 1 })
时,this.state.count还没有更新;
所以执行下一个this.setState
时,this.state.count
还是原来的0
,所以执行完三次后,相当于执行三次this.setState({ count: 1 })
;
当异步更新时,这三次结果被合并了,类似执行Object.assign({count: 1}, {count: 1}, {count: 1})
,结果为{count: 1}
,所以点击一次this.state.count
结果是每次加1
render() {
return (
)
}
merge = () => {
// 使用对象时,结果会被合并
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
this.setState({
count: this.state.count + 1
})
}
-
使用函数时,结果则不会被合并
使用函数时,会默认有两个参数:- preState: 为当前组件的
state
- props: 为父组件传递的属性
通过
preState
修改state
,然后return
返回一个对象,跟我们平常使用setState
的对象格式是一样的 - preState: 为当前组件的
render() {
return (
)
}
merge = () => {
// 使用函数,则不会被合并,执行结果+3
this.setState((preState, props) => {
return {
count: preState.count + 1
}
})
this.setState((preState, props) => {
return {
count: preState.count + 1
}
})
this.setState((preState, props) => {
return {
count: preState.count + 1
}
})
console.log('使用函数:', this.state.count)
}
可以看到,无论是用对象,还是用函数,setState
是异步返回结果是不会变的