前言
直接主题,开始前先看一段demo:
class Demo{
constructor(props) {
super(props);
this.state = {count: 0};
}
componentDidMount() {
for(let i = 0 ;i < 10;i++) {
// this.setState({
// count: this.state.count + 1
// })
// this.setState(() => {
// return {
// count: this.state.count + 1
// }
// })
// this.setState((prevState) => {
// return {
// count: prevState.count + 1
// }
// })
// console.log(this.state.count) // 0 * 10
}
}
render() {
return (
<div>
{this.state.count}
</div>
);
}
}
在平常使用react的过程中,关于setState的使用有上面几种方式,那么他们的输出结果又有什么不一样?
熟悉的同学可以马上给出答案,分别是1, 1, 10。
至于为什么,我们可以通过一段简单示例来了解下 setState 的过程。
我们常说 setState 是异步的,但实际上 setState 本身只是个同步函数,只是内部处理逻辑导致我们看到的结果是‘异步’的,这也是为什么当我们使用 console 去打印刚 setState 完的数据却发现没有变化的原因。当setSate不在react控制下的时候,setState就会同步更新state了,比如当用setTimeout包裹setState的时候。
正式开始
我们都知道setState为组件自带方法,而我们创建class组件的时候会继承react的Component,所以我们先模拟一个Component,以及函数setState,这个函数接受一个参数state,这个参数可以是对象也可以是方法,然后函数内部调用了一个state处理函数我们给它叫做setStateQueue,这个函数接受stateChange以及当前组件
class Component {
constructor(props = {}) {
this.props = props
this.state = {}
}
setState(state) {
setStateQueue(state, this)
}
}
function setStateQueue(stateChange, comp) {
// todo
}
setState方法通过一个队列机制实现state更新,我们需要定义个变量queue去保存这些更新, 这里我们还多加了个变量componentRenderQueue记录更新的组件
const queue = []
const componentRenderQueue = []
那么如何实现setState的异步呢?说到异步那就很容易想到setTimeout跟Promise
那么我们可以这么做,至于为什么可以了解下js的事件执行(event loop)。当当前更新队列为空的时候,延迟更新flush
function defer(fn) {
if(Promise) {
Promise.resolve().then(fn)
} else {
setTimeout(() => {
fn()
}, 0)
}
}
function setStateQueue(stateChange, component) {
// 将更新逻辑放至下个周期
if(queue.length === 0) {
defer(flush)
}
}
每次执行setState()的时候,先将加入更新队列, 同时记录对应的组件
function setStateQueue(stateChange, component) {
if(queue.length === 0) {
defer(flush)
}
queue.push({
stateChange,
component
})
let componentExist = componentRenderQueue.some(item => {
return item === component
})
if(!componentExist) {
componentRenderQueue.push(component)
}
}
前面的setState操作都结束了,那么就要开始真正的结果处理了. 依次执行更新队列中更新操作并记录,同时需要对上次的结果进行记录。当全都处理完成之后,对组件的state进行更新。具体逻辑如下:
function flush() {
let item = queue.shift(),
comp = componentRenderQueue.shift()
// 记录每个组件的state修改状态
const updater = {}
while(item) {
const {stateChange, component} = item
const componentName = component.constructor.name
if(!component.prevState) {
component.prevState = Object.assign({}, component.state)
}
const newState = typeof stateChange === 'function' ? stateChange(component.prevState, component.props) : stateChange
updater[componentName] = {
component,
prevState: newState
}
component.prevState = newState
item = queue.shift()
}
for(let u in updater) {
Object.assign(updater[u].component.state, updater[u].prevState)
}
while (comp) {
// 接下去走组件渲染流程
}
}
将以上代码整理合并后最终结果如下:
const queue = []
const componentRenderQueue = []
function defer(fn) {
if(Promise) {
Promise.resolve().then(fn)
} else {
setTimeout(() => {
fn()
}, 0)
}
}
function setStateQueue(stateChange, component) {
if(queue.length === 0) {
defer(flush)
}
queue.push({
stateChange,
component
})
let componentExist = componentRenderQueue.some(item => {
return item === component
})
if(!componentExist) {
componentRenderQueue.push(component)
}
}
// 更新state
function flush() {
let item = queue.shift(),
comp = componentRenderQueue.shift()
const updater = {}
while(item) {
const {stateChange, component} = item
const componentName = component.constructor.name
if(!component.prevState) {
component.prevState = Object.assign({}, component.state)
}
const newState = typeof stateChange === 'function' ? stateChange(component.prevState, component.props) : stateChange
updater[componentName] = {
component,
prevState: newState
}
component.prevState = newState
item = queue.shift()
}
for(let u in updater) {
Object.assign(updater[u].component.state, updater[u].prevState)
}
while (comp) {
// 接下去走组件渲染流程
}
}
本文通过一个简单的示例探讨React中setState的异步行为。尽管setState函数本身是同步的,但由于其内部的处理机制,使得我们在实际使用中观察到的是异步效果。文章模拟了Component和setState过程,通过队列机制解释了如何实现异步更新,并展示了核心代码实现。

1929

被折叠的 条评论
为什么被折叠?



