setState方法是同步还是异步
在react源码中他是同步的方法,通过队列的形式更新state的值,因此展现给人是异步更新的状态,但实际上它是一个同步的方法
setState两种传值格式
- 传一个对象,此时当多个setState同时存在时会使用Object.assign方式进行合并,即若出现相同的key值,后面的value会覆盖前面的,最后统一执行合并完的事件
Object.assign()
方法将所有可枚举(Object.propertyIsEnumerable()
返回 true)的自有(Object.hasOwnProperty()
返回 true)属性从一个或多个源对象复制到目标对象,返回修改后的对象。
setState({ index: 1 })
- 传一个方法,采用一个链式调用的方式,将前一个执行结果state作为后面setState的参数prevState,依次进行调用
setState((prevState, props) => {
// 获取上一个prevState做值操作
return {XX: prevState.XX + 1}
})
两种类型的调用方式(对象方式、函数方式)混用
两种方式不能混用,否则合并后就会出问题了
const root = ReactDOM.createRoot(document.getElementById('root'));
class Clock extends React.Component {
state = {
index: 0,
};
handleClick = () => {
this.setState({index: this.index + 2});
console.log('=========0,', this.state.index);
this.setState((prev, props) => ({index: prev.index + 1}), () => {console.log('函数更新后-------0', this.state.index)});
console.log('=========1,', this.state.index);
};
render() {
console.log('======render', this.state.index);
return <button onClick={this.handleClick}>Hello, world!</button>;
}
}
root.render(<Clock />)
点击button后呈现结果
image.png
原理
image.png具体操作为:
1、首先将setState中的参数partialState存储到state暂存队列中
2、判断当前是否处于批量处理状态,是,则将组件推入待更新队列,不是,则使用更新批量处理状态为ture,然后再将组件推入待更新队列(dirtyComponents)(批量更新完成后,设置批量处理状态为false,执行事务步骤)
3、调用事务(Transaction)waper方法遍历组件待更新队列dirtyComponents,执行更新
4、componentWillRecevieProps执行
5、state暂存队列合并,获取最终要更新的state,队列置空(ps:函数参数也是在这里确定值获取到prevState)
Object.assign(nextState, typeof partial === 'function' ? partial.call(inst, nextState, props, context) : partial);
6、componentShouldUpdate执行,根据返回值判断是否更新
7、componentWillUpdate执行
8、render执行
9、componentDidUpdate执行
最简单的话来讲就是state接收到一个新状态不会被立即执行,而是会被推入到pendingState队列(Queue)中,随后判断isBatchedUpdates的值,为true,则将新状态保存到dirtyComponents(脏组件)中;为false的话,遍历dirtyComponents,并调用updateComponent方法更新pengdingState中的state或props,将队列初始化
React 的更新是基于 Transaction(事务)的,Transacation 就是给目标执行的函数包裹一下,加上前置和后置的 hook ,在开始执行之前先执行 initialize hook,结束之后再执行 close hook,这样搭配上 isBatchingUpdates 这样的布尔标志位就可以实现一整个函数调用栈内的多次 setState 全部入 pending 队列,结束后统一 apply 了。
但是【setTimeout】这样的方法执行是脱离了事务的,react 管控不到,所以就没法 batch 了。
原文链接:https://blog.csdn.net/qq_39207948/article/details/113803273
总结
image.png样例
1、在同步函数中的setState
在对象中的写法
class Clock extends React.Component {
state = {
index: 0,
};
handleClick = () => {
console.log('=========0,', this.state.index);
this.setState({
index: this.state.index + 1,
}, () => {console.log('函数更新后-------0', this.state.index)});
console.log('=========1,', this.state.index);
this.setState({
index: this.state.index + 5,
}, () => {console.log('函数更新后-------1', this.state.index)});
console.log('=========2,', this.state.index);
};
render() {
console.log('======render', this.state.index);
return <button onClick={this.handleClick}>Hello, world!</button>;
}
}
ReactDOM.render(
<Clock/>,
document.getElementById('root')
);
image.png
点击button,此时,setState将设置的值推到队列里等待执行,发现有两个待更新的setState,因此在队列里增加这两个值,等待没有其他的setState后一起执行,此时同步事件的console.log已经执行过了,因此提前输出了值,随后在确认没有其他的setState时,进行render,render后再去执行两个setState的回调函数,因为有推送到队列的操作,又是对象数据,在源码内部会调用Object.assign,将对象合并,此时有同属性key值的就会被合并到一起,后面的值会覆盖前面的值,因此最后输出的是5
在函数中的写法
class Clock extends React.Component {
state = {
index: 0,
};
handleClick = () => {
console.log('=========0,', this.state.index);
this.setState((prev, props) => ({index: prev.index + 1}), () => {console.log('函数更新后-------0', this.state.index)});
console.log('=========1,', this.state.index);
this.setState((prev, props) => ({index: prev.index + 5}), () => {console.log('函数更新后-------1', this.state.index)});
console.log('=========2,', this.state.index);
};
render() {
console.log('======render', this.state.index);
return <button onClick={this.handleClick}>Hello, world!</button>;
}
}
ReactDOM.render(
<Clock/>,
document.getElementById('root')
);
image.png
点击button,两个setState同样会被推入队列中,因此跟上面一样,也会是在console.log执行完毕后才会更新state的值,即render,不同的是,此时render出来的结果是6,因为setState中使用的是函数,因此不会和对象一样使用Object.assign做合并处理,它本身不会合并,而是使用setState中更新后获取到的state值去传递给下一个setState函数,然后在进行计算,因此得到的值为6
2、在异步函数或原生dom事件中的setState
- 在16及16以下中的setState
在对象中的写法
class Clock extends React.Component {
state = {
index: 0,
};
handleClick = () => {
setTimeout(() => {
console.log('=========0,', this.state.index);
this.setState({
index: this.state.index + 1,
}, () => {console.log('函数更新后-------0', this.state.index)});
console.log('=========1,', this.state.index);
this.setState({
index: this.state.index + 5,
}, () => {console.log('函数更新后-------1', this.state.index)});
console.log('=========2,', this.state.index);
});
};
render() {
console.log('======render', this.state.index);
return <button onClick={this.handleClick}>Hello, world!</button>;
}
}
ReactDOM.render(
<Clock/>,
document.getElementById('root')
);
image.png
点击button后,看到结果为下图,即每次调用setState后都会去render,render后立即调用setState的callback函数。在异步函数中能够同步执行,能获取到上一次setState后执行的结果去作为已知值更新当前的值
在函数写法中参见下方
class Clock extends React.Component {
state = {
index: 0,
};
handleClick = () => {
setTimeout(() => {
console.log('=========0,', this.state.index);
this.setState((prev, props) => ({index: prev.index + 1}), () => {console.log('函数更新后-------0', this.state.index)});
console.log('=========1,', this.state.index);
this.setState((prev, props) => ({index: prev.index + 5}), () => {console.log('函数更新后-------1', this.state.index)});
console.log('=========2,', this.state.index);
});
};
render() {
console.log('======render', this.state.index);
return <button onClick={this.handleClick}>Hello, world!</button>;
}
}
ReactDOM.render(
<Clock/>,
document.getElementById('root')
);
image.png
由上图结果可见,在react16中,函数式结果和对象式结果一致
- 18中的setState
在对象中的写法
const root = ReactDOM.createRoot(document.getElementById('root'));
class Clock extends React.Component {
state = {
index: 0,
};
handleClick = () => {
setTimeout(() => {
console.log('=========0,', this.state.index);
this.setState({
index: this.state.index + 1,
}, () => {console.log('函数更新后-------0', this.state.index)});
console.log('=========1,', this.state.index);
this.setState({
index: this.state.index + 5,
}, () => {console.log('函数更新后-------1', this.state.index)});
console.log('=========2,', this.state.index);
});
};
render() {
console.log('======render', this.state.index);
return <button onClick={this.handleClick}>Hello, world!</button>;
}
}
root.render(<Clock />)
image.png
点击button,setTimeout中的setState也可以同步执行,但是是批量执行了,将this.setState需要更新的对象合并到了一起,因此只取了index:this.state.index + 5的那个赋值,前面的赋值被覆盖掉了,但是render只会更新一次,就是在批量处理完数据,最后更新的时候覆盖的
在函数写法中参见下方
const root = ReactDOM.createRoot(document.getElementById('root'));
class Clock extends React.Component {
state = {
index: 0,
};
handleClick = () => {
setTimeout(() => {
console.log('=========0,', this.state.index);
this.setState((prevState, props) => ({index: prevState.index + 1}),
() => {console.log('函数更新后-------0', this.state.index)}
);
console.log('=========1,', this.state.index);
this.setState((prevState, props) => ({index: prevState.index + 5}),
() => {console.log('函数更新后-------1', this.state.index)}
);
console.log('=========2,', this.state.index);
});
};
render() {
console.log('======render', this.state.index);
return <button onClick={this.handleClick}>Hello, world!</button>;
}
}
root.render(<Clock />)
image.png
点击button,会发现,他会先执行内部的同步方法,打印所有console.log后,也对setState进行了批量执行,因为最后只输出了一个render变量,但是与【对象写法】不同的是最后输出的结果是6,属于在setTimeout中将两个setState函数方法全都执行了,后面的方法执行拿到了上面的结果index,然后用第一个得到的index对下一个setState方法做值的处理,只是等所有值都更新完了,他在去进行render
- 17版本默认是与16版本做同样的处理,但是,增加了unstable_batchedUpdates,她的作用是使setState批量处理内部方法,同react18呈现同样效果使用方式大体如下
在对象中的写法
class Clock extends React.Component {
state = {
index: 0,
};
handleClick = () => {
setTimeout(() => {
ReactDOM.unstable_batchedUpdates(() => {
console.log('=========0,', this.state.index);
this.setState({
index: this.state.index + 1,
}, () => {console.log('函数更新后-------0', this.state.index)});
console.log('=========1,', this.state.index);
this.setState({
index: this.state.index + 5,
}, () => {console.log('函数更新后-------1', this.state.index)});
console.log('=========2,', this.state.index);
})
});
};
render() {
console.log('======render', this.state.index);
return <button onClick={this.handleClick}>Hello, world!</button>;
}
}
ReactDOM.render(
<Clock/>,
document.getElementById('root')
);
image.png
在函数写法中参见下方
class Clock extends React.Component {
state = {
index: 0,
};
handleClick = () => {
setTimeout(() => {
ReactDOM.unstable_batchedUpdates(() => {
console.log('=========0,', this.state.index);
this.setState((prevState, props) => ({index: prevState.index + 1}),
() => {console.log('函数更新后-------0', this.state.index)}
);
console.log('=========1,', this.state.index);
this.setState((prevState, props) => ({index: prevState.index + 5}),
() => {console.log('函数更新后-------1', this.state.index)}
);
console.log('=========2,', this.state.index);
})
});
};
render() {
console.log('======render', this.state.index);
return <button onClick={this.handleClick}>Hello, world!</button>;
}
}
ReactDOM.render(
<Clock/>,
document.getElementById('root')
);
image.png
react测试链接:https://codepen.io/gaearon/pen/zKRGpo?editors=0010
上述代码均可在其中运行
事件循环event loop
setTimeout和dom事件都属于js线程中的异步操作
componentDidMount() {
const btnEl = document.getElementById("btn");
// dom事件
btnEl.addEventListener('click', () => {
this.setState({
count: 1
});
})
}
参考文献:
https://juejin.cn/post/6844903781813993486
https://blog.csdn.net/qq_39207948/article/details/113803273
https://www.51cto.com/article/711386.html
网友评论