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

前言

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

正文

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

文档中也说明了当使用 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 的调用,造成多余的浪费。

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

您可能感兴趣的文章:

  • 深入掌握 react的 setState的工作机制
  • 深入研究React中setState源码
  • react学习笔记之state以及setState的使用
(0)

相关推荐

  • 深入研究React中setState源码

    React作为一门前端框架,虽然只是focus在MVVM中的View部分,但还是实现了View和model的绑定.修改数据的同时,可以实现View的刷新.这大大简化了我们的逻辑,只用关心数据流的变化,同时减少了代码量,使得后期维护也更加方便.这个特性则要归功于setState()方法.React中利用队列机制来管理state,避免了很多重复的View刷新.下面我们来从源码角度探寻下setState机制. 1 还是先声明一个组件,从最开始一步步来寻源: class App extends Comp

  • 深入掌握 react的 setState的工作机制

    react 是单向数据流,若想要改变已有组件的外观,我们只能通过更改组件的 props 或者更新组件的 state.在 react 项目的代码中我们操作最多的就是 this.setState 方法.下面对 setState 方法进行详细的说明. <!--more --> setState要点:react 框架为了提高性能,会对 state 的更新进行收集.合并.再进行一次批量的状态更新.这种机制常常导致一些意想不到的情况. setState 有两种调用形式: 向 setState 传递对象 s

  • react学习笔记之state以及setState的使用

    在react中通过 state 以及 setState() 来控制组件的状态. state state 是 react 中用来存储组件数据状态的,可以类比成 vue 中的 data. 1.state的作用 state是React中组件的一个对象.React把用户界面当做是状态机,想象它有不同的状态然后渲染这些状态,可以轻松让用户界面与数据保持一致. React中,更新组件的state,会导致重新渲染用户界面(不要操作DOM).简单来说,就是用户界面会随着state变化而变化. 2.state工作

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

    前言 这篇文章原标题是 3 Reasons why I stopped using React.setState ,但是我对原文作者提出的论点不是很感冒,但是作者提出的三点对 React 新手来说是很容易忽略的地方,所以我在这里只提出部分内容,而且把标题改为 使用React.setState需要注意的三点 . 正文 对 React 新手来说,使用 setState 是一件很复杂的事情.即使是熟练的 React 开发,也很有可能因为 React 的一些机制而产生一些bug,比如下面这个例子: 文档

  • 浅谈TensorFlow中读取图像数据的三种方式

    本文面对三种常常遇到的情况,总结三种读取数据的方式,分别用于处理单张图片.大量图片,和TFRecorder读取方式.并且还补充了功能相近的tf函数. 1.处理单张图片 我们训练完模型之后,常常要用图片测试,有的时候,我们并不需要对很多图像做测试,可能就是几张甚至一张.这种情况下没有必要用队列机制. import tensorflow as tf import matplotlib.pyplot as plt def read_image(file_name): img = tf.read_fil

  • 浅谈Java生成唯一标识码的三种方式

    目录 前言 正文 UUID实现唯一标识码 SnowFlake实现唯一标识码 通过时间工具生成带有业务标示的唯一标识码 前言 我们经常会遇到这样的场景,需要生成一个唯一的序列号来表明某一个数据的唯一性,在单节点的应用中我们可以简单地使用一个自增的整型来实现实现,但是在分布式情况下这个方式却存在冲突的可能性,那么有什么办法我们可以生成一个唯一的序列号呢,并且如果想使得这个序列号也能展示一些业务信息呢? 正文 UUID实现唯一标识码 UUID 的目的是让分布式系统中的所有元素,都能有唯一的辨识资讯,而

  • 浅谈webpack+react多页面开发终极架构

    webpack在单页面打包上应用广泛,以create-react-app为首的脚手架众多,单页面打包通常指的是将业务js,css打包到同一个html文件中,整个项目只有一个html文件入口,但也有许多业务需要多个页面不同的入口,比如不同的h5活动,或者需要支持seo的官方网站,都需要多个不同的html.webpack-react-multi-page架构让你可以在多页面在项目开发中自动化打包新创建页面并保证每个页面都可以热更新 ,build打包后有清晰的文件层次结构. 概览 key value

  • 浅谈在react中如何实现扫码枪输入

    触发原理 原理就是监听键盘输入,比如扫一个为6970596130126的69条形码,用扫码枪扫一下会在光标位置依次输出: 6 9 7 0 5 9 6 1 3 0 2 6 但这不是完整的,所以需要写一个函数scanEvent来整理收集到的每个编号. let code = ''; let lastTime, nextTime, lastCode, nextCode; function scanEvent(e, cb) { nextCode = e.which; nextTime = new Date

  • 浅谈从React渲染流程分析Diff算法

    React中最神奇的部分莫过于虚拟DOM,以及其高效的Diff算法.这让我们可以无需担心性能问题而"毫无顾忌"的随时"刷新"整个页面,由虚拟DOM来确保只对界面上真正变化的部分进行实际的DOM操作.React在这一部分已经做到足够透明,在实际开发中我们基本无需关心虚拟DOM是如何运作的.然而,理解其运行机制不仅有助于更好的理解React组件的生命周期,而且对于进一步优化React程序也会有很大帮助. 1.什么是虚拟DOM 在React中,render执行的结果得到的

  • 浅谈React多个setState会调用几次

    目录 1. 两个setState,调用几次? 2. 两个setState,调用的是哪一个? 3. 两个setState放在setTimeout中? 4. 总结 1. 两个setState,调用几次? 如下代码所示,state中有一个count.对按钮绑定了点击事件,事件中执行了两次setState,每次都将count的值加1. 当点击按钮时,setState会执行几次?render()会执行几次? 答案:都是1次. state = { count: 0 }; handleClick = () =

  • 浅谈React 属性和状态的一些总结

    一.属性 1.第一种使用方法:键值对 <ClaaNameA name = "Tom" /> <ClaaNameA name = {Tom} /> <ClaaNameA name = {"Tom"} /> <ClaaNameA name = {[1,2,3]} />//数组 <ClaaNameA name = {FunctionNAme} /> //定义一个函数 2.第二种方法:三个点的展开对象形式 var

  • 浅谈react.js中实现tab吸顶效果的问题

    在react项目开发中有一个需求是,页面滚动到tab所在位置时,tab要固定在顶部. 实现的思路其实很简单,就是判断当滚动距离scrollTop大于tab距离页面顶部距离offsetTop时,将tab的position变为fixed. 在react中,我在state中设置一个navTop属性,切换这个属性的值为true或者false,然后tab标签使用classnames()这个方法来利用navTop的值添加类名fixed. 一开始我是这样写的: import cs from 'classnam

  • 浅谈react.js 之 批量添加与删除功能

    最近做的CMS需要用到批量添加图片的功能:在添加文件的容器盒子内,有两个内容,分别是:添加按钮与被添加的选择文件组件. 结构分析: 被添加的组件,我们称为:UploadQiNiuFiles(七牛文件上传组件),含一个删除当前组件的删除按钮 添加按钮的事件 被添加组件存放的容器 做这个效果只需要明白三个方法的用途就OK: 直接绑定要删除组件的  deleteType(),它是调用删除index数量的方法  removeContent() //删除{qiniu}与{deleteQiNiu}内容,是把

随机推荐