最近开始从vue
转向react
,当然是从最基础(chun)开始一步一步踏实学起。
当使用到setState
这个Api
碰到了一点有意思的疑惑,顺手记录下来。
查询对应源码内容觉得比较难以理解所以在下方以一个简单Demo
记录下setState
不同状态下对应实现原理。
当然我们都清楚setState
函数是react
将对组件的state
更改排入队列进行批量更新。注意这里的批量,我们来看一段伪代码:
export default class DemoPage extends React.Component {
public state = {
name: 1,
};
// ES7方式 修改组件this 对比vue太恶心了.. 如果是传参只能使用箭头函数的方式了
private onBtnClick = () => {
this.setState({
name: this.state.name + 2,
});
this.setState({
name: this.state.name + 1,
});
};
render() {
return (
<React.Fragment>
<div>{this.state.name}</div>;
<button onClick={this.onBtnClick}>更新BTN值</button>
</React.Fragment>
);
}
}
复制代码
当我点击页面上的按钮元素时候。
此时页面上展示this.state.name
内容为2
,并不是所期待的4
。这很好理解,批量更新原则嘛,(react
内部会对state
的值进行缓存最终合并一次性更新)乍一看和Vue大同小异。
此时我们来看看另一种写法。
setTimeout
处理export default class DemoPage extends React.Component {
public state = {
name: 1,
};
// ES7方式 修改组件this 对比vue太恶心了.. 如果是传参只能使用箭头函数的方式了
private onBtnClick = () => {
setTimeout(() => {
this.setState({
name: this.state.name + 2,
});
this.setState({
name: this.state.name + 1,
});
})
};
render() {
return (
<React.Fragment>
<div>{this.state.name}</div>;
<button onClick={this.onBtnClick}>更新BTN值</button>
</React.Fragment>
);
}
}
复制代码
此时我们使用setTimeout
将两次setState
包裹起来,嗯。按照vue
中的理解,期待的结果应该还是2
。
当我天真(zu gou cai)的以为页面上会打出2
的时候,发现页面呈现结果是4
!!
what
! 怎么会这样,按照我的理解,不是说好了批量更新策略,即使在setTimeout
之后,下一个队列中应该也是批量呀。这是什么操作,为什么会这样。不行我要翻出来看看!
在一通源码(bai du)查阅下,终于搞懂了是个什么东西。为什么会这样。。
我们来看看这段伪代码,非常精简的react
关于setState
的解析,当然再高深了我也不会,我也写不出来。
// 为了方便阅读 我将相关方法都简化在了这个文件中
let isBatchingUpdate = true; // 默认页面未渲染过,react批量异步更新
function transcation(component) {
// 同步缓存状态
component.state = component.pendingState;
// 渲染页面
component.render();
// 关闭批量更新内容 渲染一次之后 关于state的操作全都是非批量更新
isBatchingUpdate = false;
}
class MyComponent {
constructor() {
this.state = { number: 0 }; // 组件自己拥有的state
this.pendingState = { ...this.state }; // react内部会缓存一份state实例对象
}
// 自己实现这个setState方法
setState(obj) {
// 更新缓存的事例对象
this.pendingState = { ...this.pendingState, ...obj };
if (!isBatchingUpdate) {
console.log('进入')
// 如果页面已经更新过了 那么isBatchingUpdate为false
// 此时就执行非批量更新 每次修改state都会进行更新state的值
transcation(this);
}
}
// 模拟页面更新方法
update() {
setTimeout(() => {
this.setState({ number: this.state.number + 1 });
this.setState({ number: this.state.number + 2 });
this.setState({ number: this.state.number + 3 });
}, 100);
transcation(this);
}
render() {
console.log(this.state.number);
}
}
const cmp = new MyComponent();
// 模拟首次渲染
cmp.update();
复制代码
下面是一段可以跑起来的伪代码,当然你也可以注释掉setTimeout
,测试出来和我们之前的验证结果一模一样。
在react
内部其实实现原理也是这样,在第一次页面渲染前(调用过一次render
方法之后)关于setState(obj)
的写法都是异步缓存更新的。
但是一旦在页面渲染之后,内部pendingState
状态改变。此时每次通过setState(obj)
更新,每次都会触发单独更新直接更新而不会异步更新。
其实内部并没有多么复杂,就是依赖与pendingState
缓存值和isBatchingUpdate
判断是否需要批量更新。(好吧其实内部没有异步什么事情,它压根没有micro
/macro
什么事情呀)。
API
总结此时我们再来看关于setState
的官方Api
就会通俗很多。
setState(obj)
首先当我们在react
内部使用setState(obj)
进行调用的时候,如果是第一次render
之前,那么所有的修改都会被缓存到pendingState
中,之后在render
中批量将pengdingState
更换为state
然后进行渲染。
所以我们每次更改state
的值并不能实施获取。
但是刚才也讲过在首次调用render
之后,再次调用setState(obj)
之后,因为isBatchingUpdate
已经打开,所以每次调用setState
就会实时修改state
的值并且进行页面渲染,此时我们就可以直接获取。
setState(callback)
react
官方提供一种setState
直接传入一个callback
的写法。callback
中支持传入一个state
参数,这个state
每次都会实时的拿到更改后的state
,其实就是和我们上文的pendingState
是一模一样的。
this.setState((state, props) => {
return {counter: state.counter + props.step};
});
复制代码
这样一段代码
this.setState((state) => ({ name:state.name+1 }))
this.setState((state) => ({ name:state.name+3 }))
this.setState((state) => ({ name:state.name+2 }))
复制代码
传入的callback
每次拿到的state
都是在修改后的新值。
callback 函数中接收的
state
和props
都保证为最新。callback 的返回值会与state
进行浅合并。注意前两种写法的执行时机都是在组件更新之前进行合并
state
并且更新到最新的state
中去。
setState(obj[,callback])
react
官方提供setState
支持传入第二个参数,它会保证在应用更新后(组件更新后执行,compnentDIdUpdate
之后)会进行执行。
this.setState({name:11},() => {
console.log('更新完毕')
})
复制代码
这种方式也会在callback
中拿到最新的state
,不过需要额外注意的是:
这样的写法类似Vue
中的nextTick
这个api
,它的callback
是会在componentDidUpdate
之后进行执行。也就是它将在 setState
完成合并并重新渲染组件后执行`。 这是和上边两种写法执行实际的不同。
当然我对于react
的探索还在继续深入,也许之后在翻回来会发现有一部分的理解很片面。当然也希望大家可以积极指出文章中的不足,共同探讨。