[Web 前端] 我不再使用React.setState的3个原因
copy from : https://blog.csdn.net/smk108/article/details/85237838
从几个月前开始,我在新开发的React组件中不再使用setState。我并没有停止使用局部组件状态,只是不再用React来管理这些state,这是很不错的一个选择。
对于React初学者来说,使用setState是比较棘手的。即使是经验丰富的React开发者,在使用React本身的状态管理机制时,也经常会出现一些比较微妙的bug,例如:

忘记setState是异步的导致的bug:控制台的输出总是落后一项
React文档总结了使用setState时可能会出现的所有问题:

总的来说,使用setState有如下3个问题:
1、setState是异步的
很多开发者刚接触React时并没有注意到setState是异步的,如果你同时修改一些state,然后马上去调用某个state,得到的是之前的值,并不是修改后的。这是使用setState时最棘手的地方。如果在使用setState时没有注意到它是异步的,就会导致一些棘手的bug。
class Select extends React.Component {
constructor(props, context) {
super(props, context)
this.state = {
selection: props.values[0]
};
}
render() {
return (
<ul onKeyDown={this.onKeyDown} tabIndex={0}>
{this.props.values.map(value =>
<li
className={value === this.state.selection ? 'selected' : ''}
key={value}
onClick={() => this.onSelect(value)}
>
{value}
</li>
)}
</ul>
)
}
onSelect(value) {
this.setState({
selection: value
})
this.fireOnSelect()
}
onKeyDown = (e) => {
const {values} = this.props
const idx = values.indexOf(this.state.selection)
if (e.keyCode === 38 && idx > 0) { /* up */
this.setState({
selection: values[idx - 1]
})
} else if (e.keyCode === 40 && idx < values.length -1) { /* down */
this.setState({
selection: values[idx + 1]
})
}
this.fireOnSelect()
}
fireOnSelect() {
if (typeof this.props.onSelect === "function")
this.props.onSelect(this.state.selection) /* not what you expected..*/
}
}
ReactDOM.render(
<Select
values={["State.", "Should.", "Be.", "Synchronous."]}
onSelect={value => console.log(value)}
/>,
document.getElementById("app")
)
乍一看,上面的代码并没有什么问题。两个事件处理函数(onKeyDown、onSelect)和一个功能函数(fireOnSelect)触发props的onSelect(如果存在)。上面的gif动态图很好的证明了Select组件的bug,当fireOnSelect被调用时,setState的操作并没有完成,因此,props的onSelect调用时传入的参数总是state中selection修改前的值。我认为React可以做的是将方法名重命名为scheduleState(注:我理解的意思是将方法置为调度状态,等setState操作完成后再执行)或者要求回调函数是必须的。
这个问题很容易修复,重点是要意识到这是个问题。
2018年1月25号注:Dan Abramov非常详细地解释了为什么setState被设计成异步的。
2、setState会引起不必要的渲染(render)
setState的第二个问题是它会触发很多不必要的重新渲染。你可以使用React性能工具提供的printWasted方法监控什么时候触发了不必要的重新渲染。粗略地说,认为一次重新渲染是必要的有以下原因:
1)state新设置的值和上一次的值完全一样。这种情况通常可以通过实现shouldComponentUpdate生命周期来解决,或者你已经在使用一些库(pure render)来解决这个问题。
2)有时state的修改与UI显示相关,但也有些例外。比如一些state用于条件可见的UI时。
3)正如Aria Buckles在2015年欧洲React会议上的演讲中提到的,一些实例状态与UI的展示完全不相关!通常一些是与管理事件监听器、计时器ID等相关的内部控制状态。
3、setState不足以管理所有的组件状态
正如上面2的最后一条所说,并不是所有组件状态都应该使用setState存储和更新。很多复杂组件通常需要使用生命周期函数来管理一些内容,例如:计时器、网络请求、事件等。使用setState管理这些复杂组件的状态不仅会触发重新渲染,还会导致一些相关的生命周期函数再次被触发,进而导致一些奇怪的状况。
使用MobX管理局部组件状态
(Surprise, surprise) 在Mendix,我们已经使用Mobx来管理所有的store。之前我们依然使用React的状态管理机制(setState)来管理局部组件状态。最近,我们连局部组件状态也换成了使用Mobx来管理。参考下面的例子:
import {observable} from "mobx"
import {observer} from "mobx-react"
@observer class Select extends React.Component {
@observable selection = null; /* MobX managed instance state */
constructor(props, context) {
super(props, context)
this.selection = props.values[0]
}
render() {
return (
<ul onKeyDown={this.onKeyDown} tabIndex={0}>
{this.props.values.map(value =>
<li
className={value === this.selection ? 'selected' : ''}
key={value}
onClick={() => this.onSelect(value)}
>
{value}
</li>
)}
</ul>
)
}
onSelect(value) {
this.selection = value
this.fireOnSelect()
}
onKeyDown = (e) => {
const {values} = this.props
const idx = values.indexOf(this.selection)
if (e.keyCode === 38 && idx > 0) { /* up */
this.selection = values[idx - 1]
} else if (e.keyCode === 40 && idx < values.length -1) { /* down */
this.selection = values[idx + 1]
}
this.fireOnSelect()
}
fireOnSelect() {
if (typeof this.props.onSelect === "function")
this.props.onSelect(this.selection) /* solved! */
}
}
ReactDOM.render(
<Select
values={["State.", "Should.", "Be.", "Synchronous."]}
onSelect={value => console.log(value)}
/>,
document.getElementById("app")
)
上面例子中的错误不会再出现:

使用同步的状态机制时,不会出现意外的错误
上面的示例代码不仅看起来更简洁,Mobx解决了所有setState相关的问题:
状态的更改会立即反映在局部组件状态中,这是我们的逻辑更简单,代码复用更容易,并且你不需要为状态尚未更新这一事实进行补偿(注:个人理解是需要额外的操作或代价)。
Mobx在运行时确定哪些可观察状态与UI渲染相关,暂时与UI无关的可观察状态不会导致组件的重新渲染,直到这些状态再次与UI相关。因此,将于UI无关的字段标记为@ observable(可观察状态)时,也不会出现上面提到的渲染代价或生命周期问题。
根据上面的解释,可渲染状态和不可渲染状态可以被统一管理。另外,存储在组件内部的状态和存储在store中的状态工作方式相同,可以达到一样的效果。这使得在必要时的组件重构、将组件内状态移动到单独的store或者将状态从store移动到组件内都很简单,在egghead的教程中有演示。
Mobx有效地将组件转换为小型store。
此外,在使用可观察状态时,不会再出现直接给state赋值这样的低级错误。并且,我们不需要在为实现shouldComponentUpdate或PureRenderMixin而担忧,Mobx已经为我们解决了这个问题。最后,你可能会问,如果我想还使用setState并且等setState完成该怎么办,Mobx允许你仍然可以使用compentDidUpdate生命周期。
如何开始使用Mobx?
开始学习使用Mobx也十分简单,你可以学习10分钟介绍或观看上面提到的视频。你可以简单地从你现有的代码中选取一个组件,使用@observer装饰它,并使用@observable引入一些可观察状态,你甚至不需要替换现有的setState调用,因为它们在使用Mobx时依然可以正常工作(虽然几分钟之内你就会感觉setState太繁琐,然后使用Mobx替换它)。如果你不喜欢使用装饰器也不需要担心,Mobx也可以配合es5一起使用。
我已经在使用Mobx管理局部组件状态,不再使用React的setState,现在React是真正的’just the view’。在我的组件中,Mobx已经同时管理局部组件状态和store中的状态,它是简洁、同步、高效、统一的。从经验中,我感觉Mobx比React的setState更容易让React初学者理解。Mobx可以我们保持组件的简洁与简单。
JSBin: 使用setState管理状态
JSBin:使用Mobx管理状态
注:1、本文为翻译Michel Weststrat(@mweststrate)的文章,原文地址:https://blog.cloudboost.io/3-reasons-why-i-stopped-using-react-setstate-ab73fc67a42e
2、在我csdn的Mobx系列中有介绍Mobx的使用。
[Web 前端] 我不再使用React.setState的3个原因的更多相关文章
- [Web 前端] webstorm 快速搭建react项目
cp from : https://blog.csdn.net/qq_39207948/article/details/79467144 前端新手如何安装webstorm ,初步搭建react项目 下 ...
- React 还是 Vue: 你应该选择哪一个Web前端框架?
学还是要学的,用的多了,也就有更多的认识了,开发中遇到选择的时候也就简单起来了. 本文作者也做了总结: 如果你喜欢用(或希望能够用)模板搭建应用,请使用Vue 如果你喜欢简单和“能用就行”的东西 ...
- web前端入坑第五篇:秒懂Vuejs、Angular、React原理和前端发展历史
秒懂Vuejs.Angular.React原理和前端发展历史 2017-04-07 小北哥哥 前端你别闹 今天来说说 "前端发展历史和框架" 「前端程序发展的历史」 「 不学自知, ...
- 部署web前端的react项目到linux服务器
部署web前端的react项目到linux服务器 项目的目录结构 ``` ├─dlls #dlls编译后的问题 ├─doc #帮助文件入口 │ ├─src │ ├─apps #各个功能模块放在这里 │ ...
- 前端迷思与React.js
前端迷思与React.js 前端技术这几年蓬勃发展, 这是当时某几个项目需要做前端技术选型时, 相关资料整理, 部分评论引用自社区. 开始吧: 目前, Web 开发技术框架选型为两种的占 80% .这 ...
- web前端面试试题总结---其他
其他问题 原来公司工作流程是怎么样的,如何与其他人协作的?如何夸部门合作的? 你遇到过比较难的技术问题是?你是如何解决的? 设计模式 知道什么是singleton, factory, strategy ...
- web前端面试试题总结---javascript篇
JavaScript 介绍js的基本数据类型. Undefined.Null.Boolean.Number.String. ECMAScript 2015 新增:Symbol(创建后独一无二且不可变的 ...
- 一个「学渣」从零开始的Web前端自学之路
从 13 年专科毕业开始,一路跌跌撞撞走了很多弯路,做过餐厅服务员,进过工厂干过流水线,做过客服,干过电话销售可以说经历相当的“丰富”. 最后的机缘巧合下,走上了前端开发之路,作为一个非计算机专业且低 ...
- 我的web前端整理和学习
知识点收藏:(边看.边记录.边写) 开直播学习:虎牙 待办事理>> 练习自我表达(把文章做成视频).技术学习总结(博客与公众号).跳出舒适圈. 前端知识体系:https://www.cnb ...
随机推荐
- linux后台执行./run.py提示python syntax error near unexpected token `('
python脚本中的#!/usr/bin/python 估计有不少人注意过一些python脚本开头有这么行东东: #!/usr/bin/python 它是用来干嘛的?貌似没有它对脚本功能也没啥 ...
- Python学习(三) —— 基本数据类型
基本数据类型 一.整数类型(int) 32位机器,整数的位数为32位,取值范围为-2**31~2**31-1,即-2147483648~2147483647 64位机器,整数的位数是64位,取值范围位 ...
- setting-url配置
参考: diango1 一. mvc和mvt模式 著名的MVC模式:所谓MVC就是把web应用分为模型(M),控制器(C),视图(V)三层:他们之间以一种插件似的,松耦合的方式连接在一起. 模型负 ...
- Codeforces 982E Billiard 扩展欧几里德
原文链接http://www.cnblogs.com/zhouzhendong/p/9055728.html 题目传送门 - Codeforces 928E 题意 一束与坐标轴平行或者成$45^\ci ...
- BZOJ1433 [ZJOI2009]假期的宿舍 二分图匹配 匈牙利算法
原文链接http://www.cnblogs.com/zhouzhendong/p/8372785.html 题目传送门 - BZOJ1433 题解 我们理一理题目. 在校的学生,有自己的床,还可以睡 ...
- springboot学习——第二集:整合Mybaits
1,Mybatis动态插入(insert)数据(使用trim标签):https://blog.csdn.net/h12kjgj/article/details/55003713 2,mybatis 中 ...
- Python操纵Excel,数据库
操作excelxlwt:写入excel表格 ,用这个之前需要先导入模块 xlwt: import xlwtxlrd:读取excel,用这个之前需要先导入模块 xlwt:import xlrd 注意:e ...
- network is unreachable 网关PING不通解决办法
所里有几台机器没有办法ping通网关,但是ping交换机里的其它机器都可以ping通,其它机器ping网关也可以ping通.那么就排出了硬件的故障,主要问题就在问题机器的路由表上了. 看一下路由表 r ...
- springboot整合springmvc上传文件
1.pom.xml <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www ...
- xss小结-从xss平台搭建到csp规则
0x00前言 xss是跨站脚本攻击,利用嵌入js代码达到‘控制’对方浏览器的作用,测试的时候我们是用alert(1)弹窗,而做CTF也好,实际中的漏洞利用也好一般是用xss获取管理员的cookie 0 ...