读完本文你讲知道:
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 };
});