setState的使用
this.setState([partialState], [callback])
[partialState]: 支持部分状态更改
this.setState({
x: 100 // 不论总共有多少状态,我们只修改了x,其余的状态不懂
})
[callback]: 在状态更改,视图更新完毕后触发执行
发生在componentDidUpdate周期函数之后,(DidUpdate会在任何状态更改后都触发执行,而回调函数的方式,可以在指定状态更新后处理一些事情)
特殊: 即便我们基于shouldComponentUpdate组织了状态/视图的更新,DidUpdate周期函数肯定不会执行了,但是我们设置的这个callback回调函数依然会被触发执行!!
类似于Vue中的 nextTick!!
import React from 'react'
class Demo extends React.Component {
state = {
x: 10,
y: 5,
z: 0
}
handle = () => {
let { x, y, z } = this.state;
// 同时修改三个状态值,只会触发一次视图更新
this.setState ({
x: x + 1,
y: y + 1,
z: z + 1
})
}
render () {
console.log('视图渲染:RENDER');
let { x, y ,z } = this.state;
return <div>
x: {x} - y: {y} - z: {z}
<br />
<button onClick = {this.handle}> 按钮 </button>
</div>
}
}
而若是我们分别来修改状态值,那这样的操作是同步还是异步的呢?
this.setState({ x: x + 1 })
this.setState({ y: y + 1 })
this.setState({ z: z + 1 })
若是同步操作,那么每次setState都将触发shouldUpdate -> willUpdate 修改状态值 -> render -> didUpdate -> callback
,视图将会更新三次,每次都将根据最新状态生成全新的virtualDOM,再去diff打补丁…
在React18中,setState在任何地方执行,都是“异步操作”
在React18中,有一套更新队列的机制
基于异步操作,实现状态的“批处理”
好处:
减少视图更新的次数,降低渲染消耗的性能
让更新的逻辑和流程更清晰稳健
**更新队列updater:**
- 在产生的私有上下文中,代码自上而下执行
- 1. 会把所有的setState操作,先加入到更新队列(只对当前上下文,同步要做的事情做处理)
- 2. 当上下文中的代码都处理完毕后,会让更新队列中的任务,统一渲染 / 更新一次 (批处理)
在React18中,setState操作都是异步的,不论是在哪执行,例如:合成事件、周期函数、定时器…
目的:实现状态的批处理(统一处理)
- 有效减少更新次数,降低性能消耗
- 有效管理代码执行的逻辑顺序
原理:利用了更新队列 updater 机制来处理的
- 在当前相同的时间段内 (浏览器此时可以处理的事情中,遇到setState会立即放入到更新队列中!)
- 此时状态 / 视图还未更新
- 当所有的代码操作结束,会“刷新队列” (通知更新队列中的任务执行):把所有放入的setState 合并在一起,执行一次视图更新 (批处理操作)
在React18 和 React16中,关于setState是同步还是异步,是有一些区别的!
React18中:不论在什么地方执行setState,它都是异步的(都是基于updater更新队列机制,实现的批处理)
React16中:如果在合成事件(jsx元素中基于onXxx绑定的事件)、周期函数中,setState的操作是异步的!!但是如果setState出现在其他异步操作中(例如:定时器、手动获取DOM元素做的事件绑定等),它将变为同步的操作(立即更新状态和让视图渲染)!!
// React16
handle = () => {
let { x, y, z } = this.state;
this.setState({ x: x + 1 }) // 异步
this.setState({ y: y + 1 }) // 异步
console.log(this.state) // {x: 10, y: 5, z: 0} -> 渲染
setTimeOut(() => {
this.setState( {z: z + 1} ) // 同步
console.log(this.state) // 渲染 -> {x: 11, y: 6, z: 1}
}, 1000)
}
// React18
handle = () => {
let { x, y, z } = this.state;
this.setState({ x: x + 1 }) // 异步
this.setState({ y: y + 1 }) // 异步
console.log(this.state) // {x: 10, y: 5, z: 0} -> 渲染
setTimeOut(() => {
this.setState( {z: z + 1} ) // 异步
console.log(this.state) // 渲染 -> {x: 11, y: 6, z: 0} -> 渲染
}, 1000)
}
若是开发中遇到需要几个状态修改之后,再去修改其他状态,此时可以通过 flushSync 实现:
flushSync :可以刷新 “updater更新队列”,也就是让修改状态的任务立即批处理一次!!
import React from 'react';
import { flushSync } from 'react-dom'
/*
this.setState((prevState) => {
// prevState:存储之前的状态值
// return的对象,就是我们想要修改的新状态值(支持修改部分状态)
})
*/
class Demo extends React.Component {
state = {
x: 0
};
handle = () => {
for (let i = 0; i < 20; i++) {
/* this.setState({
x: this.state.x + 1
}) */
this.setState(prevStaee => {
return {
x: prevState.x + 1
}
});
}
render() {
let { x } = this.state;
return <div>
x: {x}
<br />
<button onClick={this.handle}>按钮</button>
</div>
}
}
}
export default Demo;
注意:setState写成函数的区别!!