原文发表在我的博客:http://www.erichain.me/2017/04/17/2017-04-17-more-reasonable-setstate/

React 是我做前端以来接触到的第三个框架(前两个分别是 Angular 和 Vue),无论是从开发体验上和效率上,这都是一门非常优秀的框架,非常值得学习。

原谅我说了一些废话,以下是正文。

借助于 Redux,我们可以轻松的对 React 中的状态进行管理和维护,同时,React 也为我们提供了组件内的状态管理的方案,也就是 setState()。本文不会涉及到 Redux,我们将从 Component 的角度来说明你不知道的以及更合理的 setState()

先说说大家都知道的

在 React 文档的 State and Lifecycle 一章中,其实有明确的说明 setState() 的用法,向 setState() 中传入一个对象来对已有的 state 进行更新。

比如现在有下面的这样一段代码:

class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: this.state.count + 1
};
}
}

我们如果想要对这个 state 进行更新的话,就可以这样使用 setState()

this.setState({
count: 1
});

你可能不知道的

最基本的用法世人皆知,但是,在 React 的文档下面,还写着,处理关于异步更新 state 的问题的时候,就不能简单地传入对象来进行更新了。这个时候,需要采用另外一种方式来对 state 进行更新。

setState() 不仅能够接受一个对象作为参数,还能够接受一个函数作为参数。函数的参数即为 state 的前一个状态以及 props。

所以,我们可以向下面这样来更新 state:

this.setState((prevState, props) => ({ count: prevState.count + 1 }));

这样写的话,能够达到同样的效果。那么,他们之间有什么区别呢?


区别

我们来详细探讨一下为什么会有两种设置 state 的方案,他们之间有什么区别,我们应该在何时使用何种方案来更新我们的 state 才是最好的。

此处,为了能够明确的看出 state 的更新,我们采用一个比较简单的例子来进行说明。

我们设置一个累加器,在 state 上设置一个 count 属性,同时,为其增加一个 increment 方法,通过这个 increment 方法来更新 count

此处,我们采用给 setState() 传入对象的方式来更新 state,同时,我们在此处设置每调用一次 increment 方法的时候,就调用两次 setState()。具体的原因我们在后文中会讲解。

具体的代码如下:

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,所以这是为什么呢?

其实,在 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 的说明。接下来我们再看看使用函数的方式来更新 state 会有怎么样的效果呢?


我们将上面的累加器采用另外的方式来实现一次,在 setState() 的时候,我们采用传入一个函数的方式来更新我们的 state。

class IncrementByFunction extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increment = this.increment.bind(this);
} 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}>IncrementByFunction</button>
<span>{this.state.count}</span>
</div>
);
}
} ReactDOM.render(
<IncrementByFunction />,
document.getElementById('root')
);

当我们再次点击按钮的时候,就会发现,我们的累加器就会每次增加 2 了。

我们可以通过查看 React 的源代码来找出这两种更新 state 的区别 (此处只展示通过传入函数进行更新的方式的部分源码)。

在 React 的源代码中,我们可以看到这样一句代码:

this.updater.enqueueSetState(this, partialState, callback, 'setState');

然后,enqueueSetState 函数中又会有这样的实现:

queue.push(partialState);
enqueueUpdate(internalInstance);

所以,与传入对象更新 state 的方式不同,我们传入函数来更新 state 的时候,React 会把我们更新 state 的函数加入到一个队列里面,然后,按照函数的顺序依次调用。同时,为每个函数传入 state 的前一个状态,这样,就能更合理的来更新我们的 state 了。


问题所在

那么,这就是传入对象来更新 state 会导致的问题吗?当然,这只是问题之一,还不是主要的问题。

我们之前也说过,我们在处理异步更新的时候,需要用到传入函数的方式来更新我们的 state。这样,在更新下一个 state 的时候,我们能够正确的获取到之前的 state,并在在其基础之上进行相应的修改。而不是简单地执行所谓的对象合并。

所以说,我们建议,在使用 setState 的时候,采用传入函数来更新 state 的方式,这样也是一个更合理的方式。


我在 CodePen 上将这两个效果组合到了一起,感兴趣的话,你可以去试着点击一下。

See the Pen React Functional setState by Erichain (@Erichain) on CodePen.


References

Functional setState is the future of React

更合理的 setState()的更多相关文章

  1. 初学React,setState后获取到的thisstate没变,还是初始state?

    问题:(javascript)初学React,setState后获取到的thisstate没变,还是初始state?描述: getInitialState(){ return {data:[]}; } ...

  2. React 其实比 MVVM 架构更加卡顿

    React 号称通过引入 Virtual DOM 带来了性能的提升,而其实 React 之所以需要 Virtual DOM,是因为它的架构下,state 的变更是全量的,然后触发 render 返回全 ...

  3. setState的同步更新

    react中的setState特点: 是异步操作函数: 组件在还没有渲染之前, this.setState 还没有被调用: 批量执行 State 转变时让 DOM 渲染更快(相对比一个一个的setSt ...

  4. 深入研究React setState的工作机制

    前言 上个月发表了一篇 React源码学习--ReactClass,但是后来我发现,大家对这种大量贴代码分析源码的形式并不感冒.讲道理,我自己看着也烦,还不如自己直接去翻源码来得痛快.吸取了上一次的教 ...

  5. 框架基础:ajax设计方案(五)--- 集成promise规范,更优雅的书写代码

    距离上一篇博客书写,又过去了大概几个月了,这段时间暂时离开了这个行业,让大脑休息一下.一个人旅行,一个人休息,正好也去完成一个目标 --- 拥有自己的驾照.当然,也把自己晒的黑漆马虎的.不过这一段时间 ...

  6. React源码解析:setState

    先来几个例子热热身: ......... constructor(props){ super(props); this.state = { index: 0 } } componentDidMount ...

  7. [Web 前端] 我不再使用React.setState的3个原因

    copy from : https://blog.csdn.net/smk108/article/details/85237838 从几个月前开始,我在新开发的React组件中不再使用setState ...

  8. 关于React setState的实现原理(二)

    React中的Transaction 大家学过sql server的都知道我们可以批量处理sql语句,原理其实都是基于上一篇我们说的Datch Update机制.当所有的操作均执行成功,才会执行修改操 ...

  9. 为什么虚拟DOM更优胜一筹

    注意: 虚拟DOM只是实现MVVM的一种方案,或者说是视图更新的一种策略.没有虚拟DOM比MVVM更好一说. 我们回顾传统MVC框架,如backbone,它是将某个模板编译成模板函数,需要更新时,是自 ...

随机推荐

  1. GET和POST本质上有什么区别,这才是标准答案

    不知道各位读者在面试的时候,有没有被问过这个问题:"请说一下GET和POST两者的本质区别".基本上做过WEB开发的,对这个问题,都可以回答出一堆的区别. 比如: 最直接的区别,G ...

  2. IntelliJ IDEA2017 + Tomcat 设置热部署

    1.点击idea中tomcat设置 2.点击deployment查看Deploy at the server startup 中tomcat每次所运行的包是 xxxx:war 还是其他,如果是xxxx ...

  3. [BZOJ1982][POJ1740][Spoj 2021]Moving Pebbles|解题报告

    这道题的题意BZ和POJ上的都不大清楚... 大概就是给出n堆石子,以及初始每堆石子的个数 两个玩家交替操作,每个操作可以任意在一堆中取任意多的石子 然后再从这堆里拿若干个石子放到某个当前还存在的堆里 ...

  4. centOS 7 部署samba

    部署samba **每个用户有自己的目录,可以浏览内容,也可以删除** 清空防火墙规则 [root@bogon ~]# iptables -F 安装samba [root@bogon ~]# yum ...

  5. Linux设备模型(3)_Uevent【转】

    转自:http://www.wowotech.net/device_model/uevent.html 1. Uevent的功能 Uevent是Kobject的一部分,用于在Kobject状态发生改变 ...

  6. classNotFound修复

    万恶的这个bug,也是经常遇见,每一次都是在反复检查完代码之后并没有发现错误,然后开始上网百度,发现也并没有一个准确的解答.在此给出我的个人的修复bug的过程. 1.首先我是遇到了找不到entity中 ...

  7. intValue()的用法

    今天看到了Integer的这个方法,有点疑惑,查了下,做下笔记; 1.intValue()是java.lang.Number类的方法,Number是一个抽象类.Java中所有的数值类都继承它.也就是说 ...

  8. Swagger 2.0 摘要

    官网地址:http://springfox.github.io/springfox/docs/current/

  9. hdu 1115(多边形重心问题)

    Lifting the Stone Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Others ...

  10. springboot hystrix turbine 聚合监控

    1.新建Module  eureka-monitor-client 2.父级pom中添加module 3.编写eureka-monitor-client 中的pom <?xml version= ...