setState机制
在构造函数里定义state,修改state时需要用到setState ( ),不可直接通过this.state去修改状态,这样子不会更新组件(在constructor函数中可用this.state去初始化)
setState()可有两个参数,第一个参数可为对象或者方法,第二个参数可选,为一个回调函数
// 对象形式
this.setState({
state: xxx
})
// 函数形式:需要return一个对象
//默认传进prevState和props形参 表示了传进来时未被修改的state对象和props
this.setState((preState, props) => {
...
return {
state: xxx
}
})
生命周期
调用setState之后,如果shouldComponentUpdate返回true,则会依次调用更新阶段的声明周期函数,所以如果在 render 、 componentWillUpdate 和 componentDidUpdate 中调用了 setState方法,则可能会造成循环调用,最终导致浏览器内存占满后奔溃
同步与异步
setState的实现并没有涉及到任何的异步API
真正更新组件state的是flushBatchedUpdates函数,而setState不一定会调用这个函数,有可能多次setState调用一次这个函数
setState会不会立刻更新state取决于调用setState时是不是已经处于批量更新事务中
组件的生命周期函数和绑定的事件回调函数都是在批量更新事务中执行的
为了优化性能,在一个事件handler函数中,不管setState()被调用多少次,在函数执行结束以后,被归结为一次重新渲染, 这个等到最后一起执行的行为被称为batching
即:触发了setState后,可能不会立刻触发更新操作,而是将多个更改后的state排成一个队列,在渲染的时候批量处理,直到下一次render调用或shouldComponentUpdae返回false时,才会进行state的更新
可以简单理解为,更新状态发生在一次时间的同步操作后,异步操作前,且异步函数中的状态是立即更新的,整个setState函数的执行机制也是如此
// count = 0
handler = () => {
this.setState({count: this.state.count +1})
console.log(this.state.count) // 0
setTimeout(() => {
this.setState({count: this.state.count +1})
console.log(this.state.count) // 2
})
}
因此要获得最新的state
在setState中使用回调函数在异步函数中执行setState在componentDidUpdate中获取
// 使用回调函数获取最新的state
this.setState(
(prevState, props) => {
consoloe.log('第一步:' + this.state.isLiked)
return { isLiked: !prevState.isLiked }
},
() => {
console.log('第二步:' + this.state.isLiked)
}
)
console.log('第三步:' + this.state.isLiked)
// 假设state初始状态为false,运行结果为 第三步:false → 第一步:false → 第二步:true
合并
每次setState实际上都是会执行的,然而执行之后的值没有立马更新state,最终会在异步操作发生前将最终一个state进行统一更新
如果是通过state来设置新的state的话,由于还没有发生更新,每次从state里取的值都是最初的值,因此执行了三遍 count:this.state.count+1 结果仍为1
// num=0,count=0
handler = () => {
this.setState(() => {
console.log('执行了')
return {num:1, count:this.state.count+1}
})
this.setState(() => {
console.log('执行了')
return {num:2, count:this.state.count+1}
})
this.setState(() => {
console.log('执行了')
return {num:3, count:this.state.count+1}
})
}
render() {
console.log('更新了')
...
}
// 最终结果:打印'执行了'三遍,更新了一遍,表示setState都会执行,但是只会更新一次
// {num:3, count:1}
因此一般这种情况需要用到preState来处理state变化,此时需要在setState传入函数
handler = () => {
this.setState((preState) => {
return {num:1, count: preState.count + 1}
})
this.setState((preState) => {
return {num:2, count: preState.count + 1}
})
this.setState((preState) => {
return {num:3, count: preState.count + 1}
})
}
// {num:3, count:3}
在一个事件中直接使用setState:
和周围代码块是异步的关系
setState全部执行完毕后,将最终state整合才进行更新state
重新渲染在全部state都执行过后
在一个事件中的异步使用setState:
和周围代码块是同步的关系
立即更新state
重新渲染在每一次setState执行之后
// count初始值为0
handler = () => {
this.setState({count: this.state.count + 1})
console.log(this.state.count) // 0
this.setState({count: this.state.count + 1})
console.log(this.state.count) // 0
setTimeout(() => {
this.setState({count: this.state.count + 1})
console.log(this.state.count) // 2
})
setTimeout(() => {
this.setState({count: this.state.count + 1})
console.log(this.state.count) // 3
this.setState({count: this.state.count + 1})
console.log(this.state.count) // 4
})
}
componentDidUpdate() {
console.log('componentDidUpdate执行')
}
render() {
console.log('更新了')
}
// 执行结果:由setState引起了四次更新
// 可以看出,没有放在异步函数中的setState方法最终会在异步操作前整合更新state从而出现渲染组件
// 而异步函数中的setState方法则是每一次都立即更新(即使在同一个异步函数中有多个setState也是如此),且在完成一次更新后才执行后续代码