【React】为何我再也不使用setState?


几个月前,我开始中止使用React的 setState 。我并非再也不须要组件状态,并且再也不用React来管理个人组件状态。html

setState对于新手来讲不是很友好,即便是有经验的React程序员在使用setState时,也很容易出bug,好比:react

Bug产生的缘由是忘记了React的state是异步的;从日志打印延迟能够看出来。git

React的官方文档已将把使用setState可能会出现的全部问题都总结了:程序员

注意:github

永远不要直接修改this.state,须要经过调用this.setState方法来替换你修改后的值。把this.state当作不可变数据来处理。api

setState()不会立刻去改变this.state,而是会排队等待处理,因此当你调用setState()后访问this.state,有可能会返回旧的statebash

当你调用setState()时,没法保证是同步执行的,由于为了保证性能可能会被批处理。数据结构

setState()老是会触发render()进行从新渲染,除非在shouldComponentUpdata()控制了渲染逻辑。若是用了可变数据结构以及在shouldComponentUpdata()中并无控制渲染逻辑,调用setState()将不会触发从新渲染渲染。app

总的来讲,使用setState会带来三个问题:异步

1. setState是异步的

许多开发人员起初并无意识到这一点,当你设置了新的state,却发现没有变化,这是setState最诡异的地方,由于setState调用的时候看起来并非异步。好比下面的代码:

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

)复制代码

乍一看没什么问题,可是这个select组件有一个bug,上面的git图已经很好的证实了。onSelect()方法触发时老是获得前一个state.selection的值,由于setState尚未完成,fireOnSelect就被调用了。我认为应该把setState从新命名为scheduleState或者要求传入回调。

这个bug很容易修复,棘手的地方在于你很难发现它。

2. setState引发没有必要的渲染

setState的第二个问题在于它老是会触发从新渲染,不少时候这种渲染是没有必要的。你能够经过printWasted(React提供的性能工具)来检测它会在何时从新渲染。从三个方面来粗略的讲为何从新渲染有时候是没有必要的:

  • 当新的state和旧的state是同样的。能够经过shouldComponentUpdate来解决,也能够用纯渲染库来解决。

  • 只有某些时候state的改变才和渲染有关系。

  • 第三,某些时候state和视图层一点关系都没有,好比用来管理事件的监听器,定时器等相关的state

3. setState不可能管理全部组件的状态

接着上面最后那点说,不是全部的组件状态都须要经过setState来储存和更新。大多数复杂的组件一般须要管理定时器循环,接口请求,事件等等。若是用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")

)复制代码

效果以下:

用同步的组件状态的机制没有出现意想不到的bug。

上面的代码片断不只简洁美观,MobX还解决了setState的所有问题:

改变state立刻就反映到了组件state上。这让代码逻辑和代码重用变得更简单。你不用去担忧state是否更新了。

MobX在运行时候肯定哪些state与视图层关联,暂时与视图层无关的State不会引发从新渲染,直到再次关联。

因此可渲染和不可渲染状态是统一处理的。

此外,直接修改state对象的低级错误不能再犯了。还有,不要担忧是否还能用shouldComponentUpdate 和或者PureRenderMixin方法,MobX也很好的处理了。最后,你也许会想,我如何等到setState处理完成呢,你可使用compentDidUpdate生命周期。

最后:

我已经中止使用React来管理组件状态了,而是用MobX代替。如今React成了“正真”的视图层:)。

如下是实现的Dome:

原文:medium.com/@mweststrat…

相关文章
相关标签/搜索