读完本文你讲知道:
setState
不会立即改变React
组件中state
的值 (异步)setState
是异步
的,包括在setTimeout
里也是异步的 (很多文章说在setTimeout
里是同步
的,我这里用react18
测试依然是异步
)
setState
通过引发一次组件的更新过程来引发重新绘制- 多次
setState
函数调用产生的效果会合并(批处理)
setState 的特性——批处理
如果在同一周期
多次调用 setState
,后调用的 setState
将覆盖先调用的 setState
的值,例如:
// state.count === 0
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
// state.count === 1
执行 3
次 +1
,但最后只加了 1
次;若在 setTimeout
中多次调用,结果也一样
// state.count === 0
setTimeout(() => {
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
}, 0)
// state.count === 1
因为这样的操作相当于 Object.assign
,最后一个会把前面的都给覆盖
// 相当于
Object.assign(
state,
{count: state.count + 1},
{count: state.count + 1},
{count: state.count + 1},
)
同一个时期,多次调用,会合并
函数组件
和类组件
在同一时期,多次调用setState
,会合并。
const DemoState = (props) => {
let [number, setNumber] = useState(0);
const add = () => {
setNumber(number+1);
console.log(number); // 0
setNumber(number+1);
console.log(number); // 0
setNumber(number+1);
console.log(number); // 0
}
return (
<div>
<span>{ number }</span>
<button onClick={() => { add() }} >点击加 1</button>
</div>
)
}
// 0
// 0
// 0
// 页面展示 number 为 1
若上面的 3
次 +1
都放在 setTimeout
执行,也是会合并
的,并且仍然是异步 (很多文章说在 setTimeout
里是同步
的,我这里用 react18
测试依然是异步
)
const DemoState = (props) => {
let [number, setNumber] = useState(0);
const add = () => {
setTimeout(() => {
setNumber(number+1);
console.log(number); // 0
setNumber(number+1);
console.log(number); // 0
setNumber(number+1);
console.log(number); // 0
}, 0)
}
return (<div>
<span>{ number }</span>
<button onClick={() => { add() }} >点击加 1</button>
</div>)
}
// 0
// 0
// 0
// 页面展示 number 为 1
函数组件在不同时期,会合并
函数组件
多次调用 +1
操作,分别在不用时期:一个在 setTimeout 外
调用,另一个在 setTimeout 内
调用。最后合并
了,只调用了 1 次 +1
。
const DemoState = (props) => {
let [number, setNumber] = useState(0);
const add = () => {
setNumber(number+1);
console.log(number); // 0
setTimeout(() => {
setNumber(number+1);
console.log(number); // 0
}, 0)
}
return (<div>
<span>{ number }</span>
<button onClick={() => { add() }} >点击加 1</button>
</div>)
}
// 0
// 0
// 页面展示 number 为 1
类组件在不同时期,不会合并
类组件
多次调用 +1
操作,分别在不用时期:一个在 setTimeout 外
调用,另一个在 setTimeout 内
调用。最后没合并
,2 次 +1 都被调用
。
class DemoState2 extends React.Component {
constructor(props) {
super(props);
this.state = {
number: 0
}
}
add = () => {
this.setState({number: this.state.number + 1});
console.log(this.state.number); // 0
setTimeout(() => {
this.setState({number: this.state.number + 1});
console.log(this.state.number); // 1
}, 0)
}
render() {
return (
<div>
<span>{ this.state.number }</span>
<button onClick={this.add} >点击加 1</button>
</div>
)
}
}
// 0
// 1
// 页面展示 number 为 2
当前测试 react
版本:18.1.0
。
这可能是 react 的一个 bug,看看后面会不会在函数组件和类组件中保持一致。
下面看看由批处理
引发的问题:
问题 1:连续使用 setState,为什么不能实时改变
state.count = 0;
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
// state.count === 1,不是 3
因为 this.setState
方法为会进行批处理,后调的 setState
会覆盖统一周期内先调用的 setState
的值,如下所示:
state.count = 0;
this.setState({count: state.count + 2});
this.setState({count: state.count + 3});
this.setState({count: state.count + 4});
// state.count === 4
问题 2:为什么要 setState,而不是直接 this.state.xx = oo?
setState
不仅仅修改了this.state
的值,更重要的是它会触发React
的更新机制
,会进行diff
,然后将patch
部分更新到真实dom
里- 如果直接
this.state.xx = oo
的话,state
的值确实会改,但是它不会驱动React
重渲染,不会触发后续生命周期,如shouldComponentUpdate
、render
等一系列函数的调用。 - 对于
批处理
,多次setState
只产生一次
重新渲染,将对Virtual DOM
和DOM 树
操作降到最小
,用于提高性能
问题 3:那为什么会出现异步的情况呢?(为什么这么设计?)
因为性能优化
。假如每次 setState
都要更新数据,更新过程就要走五个生命周期,走完一轮生命周期再拿 render
函数的结果去做 diff
对比和更新真实 DOM
,会很耗时间。所以将每次调用都放一起做一次性处理,能降低对 DOM 的操作,提高应用性能
问题 4:那如何在表现出异步的函数里可以准确拿到更新后的 state 呢?
setState(stateChange[, callback])
setState((state, props) => stateChange[, callback])
onHandleClick() {
this.setState(
{count: this.state.count + 1,},
() => {
console.log("点击之后的回调", this.state.count); // 最新值
}
);
}
this.setState(state => {
console.log("函数模式", state.count);
return { count: state.count + 1 };
});