react的setState使用中遇到的问题

setState()更新的数据和本身预期的不一致

对 React 新手来讲,使用 setState 是一件很复杂的事情。即便是熟练的 React 开发,也颇有可能由于 React 的一些机制而产生一些bug,react文档 中也说明了当使用 setState 的时候,须要注意什么问题:css

  • 绝对不要 直接改变 this.state ,由于以后调用 setState() 可能会替换掉你作的改变。把 this.state 当作是不可变的。
  • setState() 不会马上改变 this.state ,而是建立一个即将处理的 state 转变。在调用该方法以后访问 this.state 可能会返回现有的值。
  • setState 的调用没有任何同步性的保证,而且调用可能会为了性能收益批量执行。

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

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

1. setState是异步的(不保证同步的)

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

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 。 onSelect 方法永远传递的是以前的 state.selection 值,由于当 fireOnSelect 调用的时候, setState 尚未完成它的工做。我认为 React 至少要把 setState 更名为 scheduleState 或者把回掉函数设为必须参数。异步

2. setState会形成没必要要的渲染

setState 形成的第二个问题是:每次调用都会形成从新渲染。不少时候,这些从新渲染是没必要要的。你能够用 React performance tools 中的 printWasted 来查看何时会发生没必要要渲染。可是,大概的说,没必要要的渲染有如下几个缘由:函数

  • 新的 state 其实和以前的是同样的。这个问题一般能够经过 shouldComponentUpdate 来解决。也能够用 pure render 或者其余的库赖解决这个问题。
  • 一般发生改变的 state 是和渲染有关的,可是也有例外。好比,有些数据是根据某些状态来显示的。
  • 第三,有些 state 和渲染一点关系都没有。有一些 state 多是和事件、 timer ID 有关的。

3.setState并不能颇有效的管理全部的组件状态

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

总结

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

setState 是不保证同步的

setState 是不保证同步的,是不保证同步的,是不保证同步的。重要的事情说三遍。之因此不说它是异步的,是由于 setState 在某些状况下也是同步更新的。 能够参考这篇文章this

若是须要在 setState 后直接获取修改后的值,那么有几个方案:code

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

针对于以前的例子,彻底能够在调用 fireOnSelect 的时候,传入须要的值。而不是在方法中在经过 this.state 来获取

2.使用回调函数

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

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

3.使用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

4.和渲染无关的状态尽可能不要放在 state 中来管理

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

避免没必要要的修改,当 state 的值没有发生改变的时候,尽可能不要使用 setState 。虽然 shouldComponentUpdatePureComponent 能够避免没必要要的重复渲染,可是仍是增长了一层 shallowEqual 的调用,形成多余的浪费。

参考:https://www.tuicool.com/articles/zEfEfua

相关文章
相关标签/搜索