读完本文你将知道:
setState
不会立即改变 React
组件中 state
的值 (异步)
setState
是异步
的,包括在setTimeout
里也是异步的 (很多文章说在 setTimeout
里是同步
的,我这里用 react18
测试依然是异步
)
setState
通过引发一次组件的更新过程来引发重新绘制
- 多次
setState
函数调用产生的效果会合并(批处理)
setState 的特性——批处理
如果在同一周期
多次调用 setState
,后调用的 setState
将覆盖先调用的 setState
的值,例如:
1 2 3 4 5
| this.setState({count: state.count + 1}); this.setState({count: state.count + 1}); this.setState({count: state.count + 1});
|
执行 3
次 +1
,但最后只加了 1
次;若在 setTimeout
中多次调用,结果也一样
1 2 3 4 5 6 7
| setTimeout(() => { this.setState({count: state.count + 1}); this.setState({count: state.count + 1}); this.setState({count: state.count + 1}); }, 0)
|
因为这样的操作相当于 Object.assign
,最后一个会把前面的都给覆盖
1 2 3 4 5 6 7
| Object.assign( state, {count: state.count + 1}, {count: state.count + 1}, {count: state.count + 1}, )
|
同一个时期,多次调用,会合并
函数组件
和类组件
在同一时期,多次调用setState
,会合并。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| const DemoState = (props) => { let [number, setNumber] = useState(0);
const add = () => { setNumber(number+1); console.log(number);
setNumber(number+1); console.log(number);
setNumber(number+1); console.log(number); }
return ( <div> <span>{ number }</span> <button onClick={() => { add() }} >点击加 1</button> </div> ) }
|
若上面的 3
次 +1
都放在 setTimeout
执行,也是会合并
的,并且仍然是异步 (很多文章说在 setTimeout
里是同步
的,我这里用 react18
测试依然是异步
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| const DemoState = (props) => { let [number, setNumber] = useState(0);
const add = () => {
setTimeout(() => { setNumber(number+1); console.log(number);
setNumber(number+1); console.log(number);
setNumber(number+1); console.log(number); }, 0) } return (<div> <span>{ number }</span> <button onClick={() => { add() }} >点击加 1</button> </div>) }
|
函数组件在不同时期,会合并
函数组件
多次调用 +1
操作,分别在不用时期:一个在 setTimeout 外
调用,另一个在 setTimeout 内
调用。最后合并
了,只调用了 1 次 +1
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| const DemoState = (props) => { let [number, setNumber] = useState(0);
const add = () => { setNumber(number+1); console.log(number);
setTimeout(() => { setNumber(number+1); console.log(number); }, 0) } return (<div> <span>{ number }</span> <button onClick={() => { add() }} >点击加 1</button> </div>) }
|
类组件在不同时期,不会合并
类组件
多次调用 +1
操作,分别在不用时期:一个在 setTimeout 外
调用,另一个在 setTimeout 内
调用。最后没合并
,2 次 +1 都被调用
。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
| 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);
setTimeout(() => { this.setState({number: this.state.number + 1}); console.log(this.state.number); }, 0) }
render() { return ( <div> <span>{ this.state.number }</span> <button onClick={this.add} >点击加 1</button> </div> ) } }
|
当前测试 react
版本:18.1.0
。
这可能是 react 的一个 bug,看看后面会不会在函数组件和类组件中保持一致。
下面看看由批处理
引发的问题:
问题 1:连续使用 setState,为什么不能实时改变
1 2 3 4 5
| state.count = 0; this.setState({count: state.count + 1}); this.setState({count: state.count + 1}); this.setState({count: state.count + 1});
|
因为 this.setState
方法为会进行批处理,后调的 setState
会覆盖统一周期内先调用的 setState
的值,如下所示:
1 2 3 4 5
| state.count = 0; this.setState({count: state.count + 2}); this.setState({count: state.count + 3}); this.setState({count: 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])
1 2 3 4 5 6 7 8 9 10 11 12 13
| 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 }; });
|