文章目录
- setState 的特点
- getState() 获取最新状态值
在面试中我们通常会被问到有关
setState
相关的问题。接下来我们先看几个示例,然后通过 模拟实现
setState
的行为来更加深刻的理解实例得出的结论。setState 的特点
- 批量执行:多key一次执行,对同一个key多次操作会合并,会执行最后一次
- 可能是异步的(如在生命周期、react合成事件中)
- 在定时器和原生事件中,它是同步的;
setState
可能是异步的,如果要立即获取到最新的值,有三种方式:- 传函数给setState, 或者在第二个参数回调函数中获取最新值
- 在定时器setTimeout中
- 在原生事件中(跳过了react的事件机制)
- 多次执行同一个key
class App1 extends React.Component {
state = { count: 1 }
componentDidMount() {
this.setState({ count: this.state.count++ })
this.setState({ count: this.state.count + 1 })
this.setState({ count: this.state.count + 2 })
console.log('count1', this.state.count);
//??1
// 使用定时器
setTimeout(() => {
console.log('count2', this.state.count) // ??2
})
}
handleClick = () => {
this.setState(({ count }) => {
console.log('count3', count) // ?? 3
return { count: count + 1}
})
console.log('count4', this.state.count) // ?? 4
this.setState(({ count }) => {
console.log('count5', count) // ??5
})
}
render() {
return count: {this.state.count}
}
}
我们思考下,控制台1, 2 和界面上渲染的值分别是多少呢?
当点击按钮之后,控制台 3,4, 5和界面渲染值又分别是多少?
count1: 2
count2: 4
界面显示:4
点击一次
count4: 4
count3: 4
count5: 5
界面显示:6
你分析对了吗?
我们首先理论分析一下,第一次执行时,同一个key 多次执行会被合并为一次,并执行最后一次,因此最终实际执行的是
this.state.count++
this.setState({ count: this.state.count + 2 })
所以界面上结果显示为 4 ,又因为 setState 可能是异步的,我们在 1 处 读取到的实际上是
this.state.count++
之后的值,这里需要注意的是this.state.count++
是对count本身的一种操作,不是赋予新值。我们点击一次后,函数中执行了两次 setState,执行顺序是count4: 4,count3: 4,count5: 5;当 setState 接受回调函数时,参数是最新的 state 值,回调会被一一执行,因此在第一个回调函数中的count 为 4,执行加一后,第二个回调中就是 5, 再执行加一,最终界面显示6。
OK,我们再来稍微变化一点点,我们加上第二个参数回掉:
componentDidMount() {
this.setState({ count: this.state.count++ })
this.setState({ count: this.state.count + 1 }, () => {
console.log('回调1', this.state.count)
})
this.setState({ count: this.state.count + 2 }, () => {
console.log('回调2', this.state.count)
})
console.log('count1', this.state.count)this.setState(({ count }) => {
console.log('count2', count)
return { count: count + 1 }
})this.setState({ count: this.state.count + 1 }, () => {
console.log('回调3', this.state.count)
})
console.log('count3', this.state.count)
setTimeout(() => {
console.log('count4', this.state.count)
})
如果仔细的同学,会发现这里有一个细节,如果不注意,结果就会不一样,我们看下最终的执行顺序和结果:
count1: 2
count3: 2
count2 :4
回调1: 3
回调2 :3
回调3 :3
count4 :3
界面显示结果:3
这里需要着重分析的是回调中打印的值,在count2 执行完之后 count 值变为了5,接下来执行的
this.setState({ count: this.state.count + 1 }, () => { console.log('回调3', this.state.count) })
注意,这里this.state.count 的值其实是 2,执行完之后的结果实际是 3, 所以最终我们在回调中读取的 count 都是 3。如果上面的分析没有理解,没有关系,现在我们来梳理下 setState 是如何更新状态的,如何去获取最终的 state?
getState() 获取最新状态值
setState(nextState, callback)
其实做的事情很简单,就是将接受的 nextState 和 callback 分别添加到异步队列中; // nextState 可能是对象或函数
setState(nextState, callback) {
// 添加异步队列不是每次都更新
this.$updater.addCallback(callback)
this.$updater.addState(nextState)
}
这里我们先不讲 setState 的更新具体过程,将在 一文让你不再困惑setState之原理剖析和手写(下)中详细介绍。
组件的更新和状态的收集等都是updater代理执行的,我们来定义一个 Updater , 主要实现
addCallback、addState
功能。class Updater{
constructor(instance){
this.instance = instance // 组件实例
this.pendingStates = [] // 待处理状态数组
this.pendingCallbacks = [] // 待处理回调数组
this.isPending = false
}
addState(nextState) {
if (nextState) {
// 放入更新队列
this.pendingStates.push(nextState)
// 如果当前队列没有工作则直接更新
if (!this.isPending) {
this.emitUpdate() //这里省略它的实现。。。。
}
}
}
addCallback(callback) {
if (_.isFn(callback)) {
this.pendingCallbacks.push(callback)
}
}
}
可以看到,还是简单的进行了存储,这里会先去判断当前队列是否空闲,如果没有工作,则直接更新。在真正去执行更新时,通过遍历这两个数组,来获取最新的 state 和执行回调函数;
现在我们来实现 getState() 函数
getState() {
let { instance, pendingStates } = this
let { state, props } = instance
if (pendingStates.length > 0) {
state = { ...state }
pendingStates.forEach(nextState => {
if (_.isFn(nextState)) {
nextState = nextState.call(instance, state, props)
}
state = { ...state, ...nextState }
})
pendingStates = []
}
return state
}
10行代码,在执行更新时,通过getState() 拿到最新值,通过遍历
pendingStates
, r如果 nextState
是函数类型,则直接执行,并传入当前最新的 state 和 props,如果是对象,则直接向前覆盖,这就是为何相同的key多次执行合并为一次切执行最后一次 的原因;在页面更新完成后,执行完 componentDidUpdate ,我们去调用回掉函数执行
clearCallbacks() {
let { pendingCallbacks, instance } = this
if (pendingCallbacks.length > 0) {
pendingCallbacks.forEach(callback => callback.call(instance))
this.pendingCallbacks = []
}
}
到此为止,我们再回过头去看刚才的两个栗子,是不是更好理解了。
【React高级|一文让你不再困惑setState之getState(上)】下一篇,我们看看 当页面执行 setState() 发生了什么?它的工作原理是什么?
推荐阅读
- React高级|React 之 简易实现 Fiber架构
- 笔记|手机也有生产力,手把手教你用手机开发APP
- react|react中this指向的问题
- react|React hook useEffect 与 计时器 setInterval
- 极客日报|滴滴6月或发布造车计划;英特尔顶级专家Mike Burrows跳槽AMD;Android 13开发者预览版2发布|极客头条
- react|react 递归遍历四层树结构 遍历分支中的最后一个节点_图解(数据结构中的 6 种树,你心中有数吗(...))
- react|react-transition-group的使用方法
- react|route上使用react-transition-group
- react|react-transition-group小结