在 React 文档的 State and Lifecycle 一章中,其实有明确的说明 setState() 的用法,向 setState() 中传入一个对象来对已有的 state 进行更新。 例如:html
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: this.state.count + 1
};
}
}
复制代码
咱们若是想要对这个 state 进行更新的话,就能够这样使用 setState():vue
this.setState({
count: 1
});
复制代码
可是,在 React 的文档下面,还写着,处理关于异步更新 state 的问题的时候,就不能简单地传入对象来进行更新了。这个时候,须要采用另一种方式来对 state 进行更新。react
setState() 不只可以接受一个对象做为参数,还可以接受一个函数做为参数。函数的参数即为 state 的前一个状态以及 props。git
因此,咱们能够向下面这样来更新 state:github
this.setState((prevState, props) => ({ count: prevState.count + 1 }));
复制代码
为了说明两种方式的区别,咱们举个比较简单的例子。bash
咱们设置一个累加器,在 state 上设置一个 count
属性,同时,为其增长一个 increment
方法,经过这个 increment
方法来更新 count
。异步
此处,咱们采用给 setState()
传入对象的方式来更新 state
,同时,咱们在此处设置每调用一次 increment
方法的时候,就调用两次 setState()
。函数
代码以下:ui
class IncrementByObject extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increment = this.increment.bind(this);
}
// 此处设置调用两次 setState()
increment() {
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
}
render() {
return (
<div>
<button onClick={this.increment}>IncrementByObject</button>
<span>{this.state.count}</span>
</div>
);
}
}
ReactDOM.render(
<IncrementByObject />,
document.getElementById('root')
);
复制代码
此时点击 button 的时候,count 就会更新了。可是,可能与咱们所预期的有所差异。咱们设置了点击一次就调用两次 setState(),可是,count 每一次却仍是只增长了 1,因此这是为何呢?this
其实,在 React 内部,对于这种状况,采用的是对象合并的操做,就和咱们所熟知的 Object.assign() 执行的结果同样。
例如:
Object.assign({}, { a: 2, b: 3 }, { a: 1, c: 4 });
复制代码
咱们最终获得的结果将会是 { a: 1, b: 3, c: 4 }
。对象
对象合并的操做,属性值将会以最后设置的属性的值为准,若是发现以前存在相同的属性,那么,这个属性将会被后设置的属性所替换。因此,也就不难理解为何咱们调用了两次 setState() 以后,count 依然只增长了 1 了。
本质就是下面的代码:
this.setState({
count: this.state.count + 1
});
// 同理于
Object.assign({}, this.state, { count: this.state.count + 1 });
复制代码
咱们将上面的累加器采用另外的方式来实现一次,在 setState() 的时候,咱们采用传入一个函数的方式来更新咱们的 state。
class IncrementByObject extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increment = this.increment.bind(this);
}
// 此处设置调用两次 setState()
increment() {
// 采用传入函数的方式来更新 state
this.setState((prevState, props) => ({
count: prevState.count + 1
}));
this.setState((prevState, props) => ({
count: prevState.count + 1
}));
}
render() {
return (
<div>
<button onClick={this.increment}>IncrementByObject</button>
<span>{this.state.count}</span>
</div>
);
}
}
ReactDOM.render(
<IncrementByObject />,
document.getElementById('root')
);
复制代码
当咱们再次点击按钮的时候,就会发现,咱们的累加器就会每次增长 2 了。
在 React 的源代码中,咱们能够看到这样一句代码:
this.updater.enqueueSetState(this, partialState, callback, 'setState');
复制代码
而后,enqueueSetState
函数中又会有这样的实现
queue.push(partialState);
enqueueUpdate(internalInstance);
复制代码
因此,与传入对象更新 state 的方式不一样,咱们传入函数来更新 state 的时候,React 会把咱们更新 state 的函数加入到一个队列里面,而后,按照函数的顺序依次调用。同时,为每一个函数传入 state 的前一个状态,这样,就能更合理的来更新咱们的 state 了。
咱们在处理异步更新的时候,须要用到传入函数的方式来更新咱们的 state。这样,在更新下一个 state 的时候,咱们可以正确的获取到以前的 state,并在在其基础之上进行相应的修改。而不是简单地执行所谓的对象合并。
另外最近正在写一个编译 Vue 代码到 React 代码的转换器,欢迎你们查阅。