浅谈使用React.setState需要注意的三点


Posted in Javascript onDecember 18, 2017

前言

这篇文章原标题是 3 Reasons why I stopped using React.setState ,但是我对原文作者提出的论点不是很感冒,但是作者提出的三点对 React 新手来说是很容易忽略的地方,所以我在这里只提出部分内容,而且把标题改为 使用React.setState需要注意的三点 。

正文

对 React 新手来说,使用 setState 是一件很复杂的事情。即使是熟练的 React 开发,也很有可能因为 React 的一些机制而产生一些bug,比如下面这个例子:

浅谈使用React.setState需要注意的三点

文档 中也说明了当使用 setState 的时候,需要注意什么问题:

注意:

绝对不要 直接改变 this.state ,因为之后调用 setState() 可能会替换掉你做的改

变。把 this.state 当做是不可变的。

setState() 不会立刻改变 this.state ,而是创建一个即将处理的 state 转变。在调用该方法之后访问 this.state 可能会返回现有的值。

对 setState 的调用没有任何同步性的保证,并且调用可能会为了性能收益批量执行。

setState() 将总是触发一次重绘,除非在 shouldComponentUpdate() 中实现了条件渲染逻辑。如果可变对象被使用了,但又不能在 shouldComponentUpdate() 中实现这种逻辑,仅在新 state 和之前的 state 存在差异的时候调用 setState() 可以避免不必要的重新渲染。

总结出来,当使用 setState 的时候,有三个问题需要注意:

1. setState是异步的(译者注:不保证同步的)

很多开发刚开始没有注意到 setState 是异步的。如果你修改一些 state ,然后直接查看它,你会看到之前的 state 。这是 setState 中最容易出错的地方。 setState 这个词看起来并不像是异步的,所以如果你不假思索的用它,可能会造成 bugs 。下面这个例子很好的展示了这个问题:

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")
)

第一眼看上去,这个代码似乎没有什么问题。两个事件处理中调用 onSelect 方法。但是,这个 Select 组件中有一个 bug 很好的展现了之前的 GIF 图。 onSelect 方法永远传递的是之前的 state.selection 值,因为当 fireOnSelect 调用的时候, setState 还没有完成它的工作。我认为 React 至少要把 setState 改名为 scheduleState 或者把回掉函数设为必须参数。

这个bug很容易修改,最难的地方在于你要知道有这个问题。

2. setState会造成不必要的渲染

setState 造成的第二个问题是:每次调用都会造成重新渲染。很多时候,这些重新渲染是不必要的。你可以用 React performance tools 中的 printWasted 来查看什么时候会发生不必要渲染。但是,大概的说,不必要的渲染有以下几个原因:

  1. 新的 state 其实和之前的是一样的。这个问题通常可以通过 shouldComponentUpdate 来解决。也可以用 pure render 或者其他的库赖解决这个问题。
  2. 通常发生改变的 state 是和渲染有关的,但是也有例外。比如,有些数据是根据某些状态来显示的。
  3. 第三,有些 state 和渲染一点关系都没有。有一些 state 可能是和事件、 timer ID 有关的。

3.setState并不能很有效的管理所有的组件状态

基于上面的最后一条,并不是所有的组件状态都应该用 setState 来进行保存和更新的。复杂的组件可能会有各种各样的状态需要管理。用 setState 来管理这些状态不但会造成很多不需要的重新渲染,也会造成相关的生命周期钩子一直被调用,从而造成很多奇怪的问题。

后话

在原文中作者推荐了一个叫做 MobX 的库来管理部分状态,我不是很感冒,所以我就不介绍。如果感兴趣的,可以通过最上面的链接看看原文中的介绍。

基于上面提出的三点,我认为新手应该注意的地方是:

setState 是不保证同步的

setState 是不保证同步的,是不保证同步的,是不保证同步的。重要的事情说三遍。之所以不说它是异步的,是因为 setState 在某些情况下也是同步更新的。 可以参考这篇文章

如果需要在 setState 后直接获取修改后的值,那么有几个方案:

传入对应的参数,不通过 this.state 获取

针对于之前的例子,完全可以在调用 fireOnSelect 的时候,传入需要的值。而不是在方法中在通过 this.state 来获取

使用回调函数

setState 方法接收一个 function 作为回调函数。这个回掉函数会在 setState 完成以后直接调用,这样就可以获取最新的 state 。对于之前的例子,就可以这样:

this.setState({
 selection: value
}, this.fireOnSelect)

使用setTimeout

在 setState 使用 setTimeout 来让 setState 先完成以后再执行里面内容。这样子:

this.setState({
 selection: value
});
setTimeout(this.fireOnSelect, 0);

直接输出,回调函数, setTimeout 对比

componentDidMount(){
  this.setState({val: this.state.val + 1}, ()=>{
   console.log("In callback " + this.state.val);
  });

  console.log("Direct call " + this.state.val);  
  setTimeout(()=>{
   console.log("begin of setTimeout" + this.state.val);
    this.setState({val: this.state.val + 1}, ()=>{
     console.log("setTimeout setState callback " + this.state.val);
    });

   setTimeout(()=>{
    console.log("setTimeout of settimeout " + this.state.val);
   }, 0);

   console.log("end of setTimeout " + this.state.val);
  }, 0);
 }

如果val默认为0, 输入的结果是:

Direct call 0
In callback 1
begin of setTimeout 1
setTimeout setState callback 2
end of setTimeout 2
setTimeout of settimeout 2

和渲染无关的状态尽量不要放在 state 中来管理

通常 state 中只来管理和渲染有关的状态 ,从而保证 setState 改变的状态都是和渲染有关的状态。这样子就可以避免不必要的重复渲染。其他和渲染无关的状态,可以直接以属性的形式保存在组件中,在需要的时候调用和改变,不会造成渲染。

避免不必要的修改,当 state 的值没有发生改变的时候,尽量不要使用 setState 。虽然 shouldComponentUpdate 和 PureComponent 可以避免不必要的重复渲染,但是还是增加了一层 shallowEqual 的调用,造成多余的浪费。

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
JQuery获取当前屏幕的高度宽度的实现代码
Jul 12 Javascript
js写出遮罩层登陆框和对联广告并自动跟随滚动条滚动
Apr 29 Javascript
多种方法实现360浏览器下禁止自动填写用户名密码
Jun 16 Javascript
JQuery radio(单选按钮)操作方法汇总
Apr 15 Javascript
JS实现的简洁纵向滑动菜单(滑动门)效果
Oct 19 Javascript
简单谈谈javascript中this的隐式绑定
Feb 22 Javascript
使用JavaScript判断用户输入的是否为正整数(两种方法)
Feb 05 Javascript
JS简单验证上传文件类型的方法
Apr 17 Javascript
用js实现每隔一秒刷新时间的实例(含年月日时分秒)
Oct 25 Javascript
解决vue select当前value没有更新到vue对象属性的问题
Aug 30 Javascript
微信小程序调用wx.getImageInfo遇到的坑解决
May 31 Javascript
JavaScript/TypeScript 实现并发请求控制的示例代码
Jan 18 Javascript
vue 项目如何引入微信sdk接口的方法
Dec 18 #Javascript
微信小程序实现给嵌套template模板传递数据的方式总结
Dec 18 #Javascript
微信小程序实现页面跳转传值以及获取值的方法分析
Dec 18 #Javascript
10个在JavaScript开发中常遇到的BUG
Dec 18 #Javascript
详解webpack与SPA实践之开发环境搭建
Dec 18 #Javascript
javaScript中的空值和假值
Dec 18 #Javascript
浅谈Webpack自动化构建实践指南
Dec 18 #Javascript
You might like
深入phpMyAdmin的安装与配置的详细步骤
2013/05/07 PHP
探讨:使用XMLSerialize 序列化与反序列化
2013/06/08 PHP
thinkphp实现附件上传功能
2017/05/26 PHP
统一接口:为FireFox添加IE的方法和属性的js代码
2007/03/25 Javascript
基于jquery实现状态限定编辑的代码
2012/02/11 Javascript
jQuery+CSS3实现仿花瓣网固定顶部位置带悬浮效果的导航菜单
2016/09/21 Javascript
Webpack执行命令参数详解
2017/06/17 Javascript
Angular2 组件通信的实例代码
2017/06/23 Javascript
jQuery图片缩放插件smartZoom使用实例详解
2017/08/25 jQuery
Node.js net模块功能及事件监听用法分析
2019/01/05 Javascript
vue与bootstrap实现简单用户信息添加删除功能
2019/02/15 Javascript
vue 地区选择器v-distpicker的常用功能
2019/07/23 Javascript
详解基于Vue/React项目的移动端适配方案
2019/08/23 Javascript
jQuery实现动态向上滚动
2020/12/21 jQuery
[01:18:21]EG vs TNC Supermajor小组赛B组败者组第一轮 BO3 第一场 6.2
2018/06/03 DOTA
python使用PIL缩放网络图片并保存的方法
2015/04/24 Python
在Python中操作字典之fromkeys()方法的使用
2015/05/21 Python
基于循环神经网络(RNN)的古诗生成器
2018/03/26 Python
Python编程深度学习计算库之numpy
2018/12/28 Python
python编写计算器功能
2019/10/25 Python
浅析Python3 pip换源问题
2020/01/06 Python
pandas DataFrame运算的实现
2020/06/14 Python
瑞士国际航空官网:SWISS
2016/07/21 全球购物
全球速卖通巴西站点:Aliexpress巴西
2016/08/24 全球购物
Trunki英国官网:儿童坐骑式行李箱
2017/05/30 全球购物
Marlies Dekkers内衣美国官方网上商店:高端内衣品牌
2018/11/12 全球购物
美国在线鞋类零售商:LifeStride
2019/06/09 全球购物
数控技术与应用毕业生自荐信
2013/09/24 职场文书
工程师岗位职责规定
2014/02/26 职场文书
大学生村官考核材料
2014/05/23 职场文书
项目经理任命书范本
2014/06/05 职场文书
面试自我评价范文
2014/09/17 职场文书
反腐倡廉剖析材料
2014/09/30 职场文书
银行业务授权委托书
2014/10/10 职场文书
总结一下关于在Java8中使用stream流踩过的一些坑
2021/06/24 Java/Android
JavaScript架构搭建前端监控如何采集异常数据
2022/06/25 Javascript