原文发表在我的博客:www.erichain.me/2017/04/17/…

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() {// 采用传入函数的方式来更新 statethis.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. 如何将某个groupbox中的数据赋值到另一个groupbox_React中的数据和数据流

    第2章大致介绍了React.我们花了些时间学习了React,了解它的设计和API背后的一些概念,我们甚至还逐步说明了如何用React组件构建一个简单注释框.在第4章中,我们将开始更全面地使用组件并开始 ...

  3. 把这304道React的面试题刷完,前端面试没有在怕的!

    Core React 什么是 React? React 是一个开源前端 JavaScript 库,用于构建用户界面,尤其是单页应用程序.它用于处理网页和移动应用程序的视图层.React 是由 Face ...

  4. React 面试题 回答

    原文https://github.com/semlinker/reactjs-interview-questions 本项目的面试题来源于 sudheerj/reactjs-interview-que ...

  5. 从设计的角度看 Redux

    原文地址:https://segmentfault.com/a/1190000018943038 你知道 Redux 真正的作用远不止状态管理吗? 你是否想要了解 Redux 的工作原理? 让我们深入 ...

  6. [译] 从设计师的角度看 Redux

    原文链接: www.smashingmagazine.com/2018/07/red- 推荐理由: 插图大爱 没有空洞的概念 也没有海量的代码! 内容概要: 你是否知道 Redux 的真正威力远不止状 ...

  7. 中国五十六个民族简介

    56个民族是中华人民共和国灿烂星空中(五十六个星座). 中华民族共包括56个民族,汉族是中国的主体民族,占全部人口的91.51%,其他还有55个民族,占8.49%(第六次人口普查).汉族和55个少数民 ...

  8. react引入多个图片_重新引入React:v16之后的每个React更新都已揭开神秘面纱。

    react引入多个图片 In this article (and accompanying book), unlike any you may have come across before, I w ...

  9. react的导出是怎么实现的_22 个让 React 开发更高效更有趣的工具

    英文 | https://dev.to/jsmanifest/22-miraculous-tools-for-react-developers-in-2019-4i46翻译 | https://www ...

最新文章

  1. spring 定时器任务深入理解
  2. AI:2020年6月21日北京智源大会演讲分享之15:15-15:40黄萱菁教授《自然语言处理中的表示学习》
  3. RocketMQ API使用简介、拉取机制
  4. 确保Kubernetes环境安全的3种最佳实践
  5. 崩坏3支持鸿蒙系统没,崩坏3鸿蒙版
  6. php天津旅游设计网站作品
  7. java list api_Java核心API -- 6(Collection集合List、Set、ArrayList、HashSet)
  8. 硬件开发板-嵌入式开发
  9. 》》css3--动画
  10. mysql下载和安装详细教程
  11. 计算机开机高级设置密码,给电脑设置开机密码
  12. 城镇化进程中的粮食生产问题
  13. C语言 7-3 统计大写字母个数
  14. 单片机位寻址举例_基于80C51单片机位寻址编程
  15. docker学习笔记(二)docker常用命令
  16. 2000-2020年上市公司制造业数据/制造业上市公司数据
  17. Goroutine调度器及面试精选
  18. GCN - Semi-Supervised Classification with Graph Convolutional Networks 用图卷积进行半监督节点分类 ICLR 2017
  19. 如何使用 Javascript 截断/切片/修剪字符串中的最后一个字符?
  20. 包装成悲伤消费的骗局正在收割午夜的年轻人

热门文章

  1. java表单上下左右滚动_怎么在网页中实现表格上下左右滚动
  2. rapter求n的阶乘流程图_RAPTOR程序设计例题参考答案
  3. 双线性内插怎么缩小_汗蒸桶怎么选择才是最好的
  4. python3 线程隔离_Python并发编程之线程中的信息隔离(五)
  5. 【知识星球】有三AI编程与开源框架正式开通
  6. 中国节能减排行业十四五运行现状及建设应用价值分析报告2021-2027年
  7. Application Desktop Toolbars 桌面工具栏
  8. VB中使用PNG格式图片的一种新方法
  9. GPU Gems 3
  10. 探偵ガリレオーくさる3