欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-07-10 23:17:16
前言 这篇文章原标题是 3 reasons why i stopped using react.setstate ,但是我对原文作者提出的论点不是很感冒,但是作者提出的三...

前言

这篇文章原标题是 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 的调用,造成多余的浪费。

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