在使用React过程中,可以使用this.state来访问需要的某些状态,但是需要更新或者修改state时,一般而言,我们都会使用setState()函数,从而达到更新state的目的,setState()函数执行会触发页面重新渲染UI。
异步与回调
首先,需要我们注意的是,setState
函数是“异步”的。在它立即执行后,无法获取最新的state
值。这是因为React对state
的所有改变进行合并处理之后,才会去计算新的虚拟dom,再根据最新的虚拟dom去重新渲染真实dom。
1. 回调:
setState(updater[, callback]) //
updater是要改变的state对象,callback是state导致的页面重新渲染的回调,等价于componentDidUpdate
2. 区别:
//不使用回调
this.state = {preState: false};
this.setState({preState: true});
console.log(this.state.preState); // false
//使用回调
this.state = {newState: false};
this.setState(
{
newState: true
}, ()=> {
console.log(newState); // true
}
);
同步
其次,我们需要了解的是,setState并不是真正的“异步”。它只是模拟了异步行为,在React中会维护一个isBatchingUpdates
标识,用来标记更新行为,依此判断是直接更新还是暂存state
。在onClick
、onChange
等React合成事件中,state
会被控制在合成事件和钩子函数执行之后更新,因此在合成事件和钩子函数中无法拿到最新的值(在回调中可以拿到),形成了所谓的“异步”。
那在什么场景下,它是同步的呢?
1. 原生事件中
class App extends Component {
state = { count: 0 }
change = () => {
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) // 1
}
componentDidMount() {
document.body.addEventListener('click', this.change, false)
}
render() {
return (
<div>
{`Count is: ${this.state.count}`}
</div>
)
}
}
2. setTimeout中
class App extends Component {
state = { count: 0 }
componentDidMount() {
setTimeout(() => {
this.setState({ count: this.state.count + 1 })
console.log(this.state.count) // 1
}, 0)
}
render() {
return (
<div>
{`Count is: ${this.state.count}`}
</div>
)
}
}
总结
- setTimeout和原生事件都会直接去更新state,因此可以立即得到最新state。
- 合成事件和React生命周期函数中,受React控制的,其会将isBatchingUpdates设置为 true,从而走“异步”行为。
- setState更新状态时可能会导致页面不必要的重新渲染,影响加载。
- setState管理大量组件状态也许会导致不必要的生命周期函数钩子调用。
参考链接:https://juejin.im/post/5bf1444cf265da614a3a1660