setState 的执行是一个 '异步' 的过程,为什么会将这两个字用引号代替,肯定其中有一定的道理,下面对 setState 进行分析
setState 源码分析(v16.10.0)
React 源码,setState 中传入两个参数 partialState, callback:
-
partialState在字面意思理解应该是部分状态,注释说明是这样的Next partial state or function to produce next partial state to be merged with current state.,大概翻译是下一个部分状态或函数,以产生下一个要与当前状态合并的部分状态。。实际上,在项目中this.setState({})更新指定state时,其他的state是不受影响的,且所有的state与当前的更新的state做了合并。 -
callback回调函数,意指状态更新后的回调,在该函数中获取最新的state。 -
setState处理过程
首先调用了 invariant(), 其次调用了 this.updater.enqueueSetState()
/packages/shared/invariant.js, 这个方法就是判断 partialState 的类型是否正确,抛出错误,附上源码:
/packages/react-dom/src/server/ReactPartialRenderer.js,把即将更新的 state push 到了 queue 中,在 new Component 时,将 updater 传进去,附上源码:
queue 应该是 React 提升性能的关键。因为并不是每次调用 setState, React 都会立马更新,而是每次调用 setState, React 只是将其 push 到了待更新的 queue 中,附上源码:
/packages/react-reconciler/src/ReactFiberClassComponent.js 中的 enqueueSetState;
/packages/react-reconciler/src/ReactUpdateQueue.js 中的 enqueueUpdate;
过程分析
- 钩子函数及合成方法 -- setState
import React from 'react';
import {Button} from 'antd';
class SetState extends React.Component {
state = { val: 0 }
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的还是更新前的值 --> 0
}
increment = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新前的val --> 1
}
render() {
return (
<Button type="primary" onClick={this.increment}>
钩子函数及合成方法 --> {`Counter is: ${this.state.val}`}
</Button>
)
}
}
export default SetState;
- 原生js事件 -- setState
import React from 'react';
import {Button} from 'antd';
class SetState extends React.Component {
constructor(props) {
super(props);
this.state = {
val: 0
}
}
componentDidMount() {
document.body.addEventListener('click', this.changeValue, false)
}
changeValue = () => {
this.setState({ val: this.state.val + 1 })
console.log(this.state.val) // 输出的是更新后的值 --> 1
}
render() {
return (
<Button type="peimary">
原生js事件 --> {`Counter is: ${this.state.val}`}
</Button>
)
}
}
export default SetState;
- setTimeout 定时器 -- setState
import React from 'react';
import {Button} from 'antd';
class SetState extends React.Component {
constructor(props) {
super(props);
this.state = {
val: 0
}
}
componentDidMount() {
setTimeout(_ => {
console.log('执行->第二步', this.state.val) // 输出更新前的值 --> 0
this.setState({ val: this.state.val + 1 })
console.log('执行->第三步', this.state.val) // 输出更新后的值 --> 1
}, 0);
console.log('执行->第一步', this.state.val) // 输出更新前的值 --> 0
}
render() {
return (
<Button type="primary">
setTimeout 定时器 --> {`Counter is: ${this.state.val}`}
</Button>
)
}
}
export default SetState;
- 批量更新 -- setState
import React from 'react';
import {Button} from 'antd';
class SetState extends React.Component {
constructor(props) {
super(props);
this.state = {
val: 0
}
}
batchUpdates = () => {
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
this.setState({ val: this.state.val + 1 })
}
render() {
return (
<Button type="primary" onClick={this.batchUpdates}>
批量更新 --> {`Counter is: ${this.state.val}`}
</Button>
)
}
}
export default SetState;
- 钩子函数及 setTimmout 定时器 -- setState 的执行结果
import React from 'react';
import {Button} from 'antd';
class SetState extends React.Component {
constructor(props) {
super(props);
this.state = {
val: 0
}
}
// 钩子函数中的 setState 无法立马拿到更新后的值;
// setState 批量更新的策略;
// setTimmout 中 setState 是可以同步拿到更新结果
componentDidMount() {
this.setState({ val: this.state.val + 1 })
console.log('第一步:', this.state.val); // 0
this.setState({ val: this.state.val + 1 })
console.log('第二步:', this.state.val); // 0
setTimeout(_ => {
this.setState({ val: this.state.val + 1 })
console.log('第三步:', this.state.val); // 2
this.setState({ val: this.state.val + 1 })
console.log('第四步:', this.state.val); // 3
}, 0)
}
render() {
return (
<Button type="primary">
钩子函数及 setTimmout 定时器 --> {`Counter is: ${this.state.val}`}
</Button>
)
}
}
export default SetState;
setState 第二个参数是一个回调函数
如果希望在 setState 时就能获取到最新值,可以在 setState 的回调函数中获取最新结果
this.setState(
{
data: newData
},
() => {
console.log('最新的 state 值', me.state.data)
}
);
总结
setState所谓的 '异步',实际上只在合成事件一生命周期函数中存在,在原生js时间与定时器中任然是同步变化的,setState中还做了批量更新的优化。如果希望在this.setState({})后及时获取到最新state,可以在其回调函数中获取。- 在 定时器 或者
async函数中应尽量避免多次调用setState,而是合成一个 setState,否则会多次渲染 - hooks 中使用 useState 也会存在异步/定时器中执行多个,渲染多次的问题,方案详见 github.com/facebook/re…