原文发表在我的博客: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. JSON各种转换总结

    JSONObject,JSONArray,Map,String之间转换 http://blog.csdn.net/superit401/article/details/51727739 1.Strin ...

  2. loj6043 「雅礼集训 2017 Day7」蛐蛐国的修墙方案

    传送门:https://loj.ac/problem/6043 [题解] 我们考虑这是个置换,所以一定形成了很多不相交的环. 对于每个环,我们只能选一段.不选.选一段.不选这样交替下去. 显然只有偶环 ...

  3. swift中_的用法,忽略默认参数名。

    swift中默认参数名除了第一个之外,其他的默认是不忽略的,但是如果在参数的名字前面加上_,就可以忽略这个参数名了,虽然有些麻烦,但是这种定义也挺好,而且不想知道名字或者不想让别人知道名字的或者不用让 ...

  4. 关于Javascript 闭包的理解

    一.什么是闭包? 官方”的解释是:闭包是一个拥有许多变量和绑定了这些变量的环境的表达式(通常是一个函数),因而这些变量也是该表达式的一部分.相信很少有人能直接看懂这句话,因为他描述的太学术.其实这句话 ...

  5. Mac git

    多次提交代码,与合并的工作量成反比. in terminal window git status:查看git的状态 git add -A: 把所有不再track里的文件加入进去/ git add -u ...

  6. JSP的简便写法

    aaarticlea/png;base64,iVBORw0KGgoAAAANSUhEUgAAAMIAAAFmCAIAAACwedIqAAAWfklEQVR4nO2d7XPV1p3H77+x+3JfpJ ...

  7. Django-csrf跨站请求伪造

    方式一: 下列代码插入ajax提交之$.ajaxSetup({ data: {csrfmiddlewaretoken: '{{ csrf_token }}' }, }); 注意:该代码中包含模板渲染语 ...

  8. 网络编程学习笔记--1.socket可读可写条件

    转至 :http://blog.csdn.net/majianfei1023/article/details/45788591 socket可读可写条件,经常做为面试题被问,因为它考察被面试者对网络编 ...

  9. serialVersionUID的作用(zz)

    http://www.cnblogs.com/guanghuiqq/archive/2012/07/18/2597036.html 简单来说,Java的序列化机制是通过在运行时判断类的serialVe ...

  10. 关于mysql数据库的表概况 ,查看表状态

    SHOW TABLE STATUS FROM `DB_NAME` WHERE  ENGINE IS NOT NULL; SHOW TABLE STATUS FROM `DB_NAME`  WHERE ...