[Web 前端] 我再也不使用React.setState的3个缘由

copy from : https://blog.csdn.net/smk108/article/details/85237838html

 

从几个月前开始,我在新开发的React组件中再也不使用setState。我并无中止使用局部组件状态,只是再也不用React来管理这些state,这是很不错的一个选择。react

对于React初学者来讲,使用setState是比较棘手的。即便是经验丰富的React开发者,在使用React自己的状态管理机制时,也常常会出现一些比较微妙的bug,例如:git

忘记setState是异步的致使的bug:控制台的输出老是落后一项github

React文档总结了使用setState时可能会出现的全部问题:网络

总的来讲,使用setState有以下3个问题:app

一、setState是异步的less

不少开发者刚接触React时并无注意到setState是异步的,若是你同时修改一些state,而后立刻去调用某个state,获得的是以前的值,并非修改后的。这是使用setState时最棘手的地方。若是在使用setState时没有注意到它是异步的,就会致使一些棘手的bug。异步

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

乍一看,上面的代码并无什么问题。两个事件处理函数(onKeyDown、onSelect)和一个功能函数(fireOnSelect)触发props的onSelect(若是存在)。上面的gif动态图很好的证实了Select组件的bug,当fireOnSelect被调用时,setState的操做并无完成,所以,props的onSelect调用时传入的参数老是state中selection修改前的值。我认为React能够作的是将方法名重命名为scheduleState(注:我理解的意思是将方法置为调度状态,等setState操做完成后再执行)或者要求回调函数是必须的。函数

这个问题很容易修复,重点是要意识到这是个问题。工具

2018年1月25号注:Dan Abramov很是详细地解释了为何setState被设计成异步的

二、setState会引发没必要要的渲染(render)

setState的第二个问题是它会触发不少没必要要的从新渲染。你可使用React性能工具提供的printWasted方法监控何时触发了没必要要的从新渲染。粗略地说,认为一次从新渲染是必要的有如下缘由:

1)state新设置的值和上一次的值彻底同样。这种状况一般能够经过实现shouldComponentUpdate生命周期来解决,或者你已经在使用一些库(pure render)来解决这个问题。

2)有时state的修改与UI显示相关,但也有些例外。好比一些state用于条件可见的UI时。

3)正如Aria Buckles在2015年欧洲React会议上的演讲中提到的,一些实例状态与UI的展现彻底不相关!一般一些是与管理事件监听器、计时器ID等相关的内部控制状态。

三、setState不足以管理全部的组件状态

正如上面2的最后一条所说,并非全部组件状态都应该使用setState存储和更新。不少复杂组件一般须要使用生命周期函数来管理一些内容,例如:计时器、网络请求、事件等。使用setState管理这些复杂组件的状态不只会触发从新渲染,还会致使一些相关的生命周期函数再次被触发,进而致使一些奇怪的情况。

使用MobX管理局部组件状态

(Surprise, surprise) 在Mendix,咱们已经使用Mobx来管理全部的store。以前咱们依然使用React的状态管理机制(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")
)

上面例子中的错误不会再出现:

使用同步的状态机制时,不会出现意外的错误

上面的示例代码不只看起来更简洁,Mobx解决了全部setState相关的问题:

状态的更改会当即反映在局部组件状态中,这是咱们的逻辑更简单,代码复用更容易,而且你不须要为状态还没有更新这一事实进行补偿(注:我的理解是须要额外的操做或代价)。

Mobx在运行时肯定哪些可观察状态与UI渲染相关,暂时与UI无关的可观察状态不会致使组件的从新渲染,直到这些状态再次与UI相关。所以,将于UI无关的字段标记为@ observable(可观察状态)时,也不会出现上面提到的渲染代价或生命周期问题。

根据上面的解释,可渲染状态和不可渲染状态能够被统一管理。另外,存储在组件内部的状态和存储在store中的状态工做方式相同,能够达到同样的效果。这使得在必要时的组件重构、将组件内状态移动到单独的store或者将状态从store移动到组件内都很简单,在egghead的教程中有演示。

Mobx有效地将组件转换为小型store。

此外,在使用可观察状态时,不会再出现直接给state赋值这样的低级错误。而且,咱们不须要在为实现shouldComponentUpdate或PureRenderMixin而担心,Mobx已经为咱们解决了这个问题。最后,你可能会问,若是我想还使用setState而且等setState完成该怎么办,Mobx容许你仍然可使用compentDidUpdate生命周期。

如何开始使用Mobx?

开始学习使用Mobx也十分简单,你能够学习10分钟介绍或观看上面提到的视频。你能够简单地从你现有的代码中选取一个组件,使用@observer装饰它,并使用@observable引入一些可观察状态,你甚至不须要替换现有的setState调用,由于它们在使用Mobx时依然能够正常工做(虽然几分钟以内你就会感受setState太繁琐,而后使用Mobx替换它)。若是你不喜欢使用装饰器也不须要担忧,Mobx也能够配合es5一块儿使用

我已经在使用Mobx管理局部组件状态,再也不使用React的setState,如今React是真正的’just the view’。在个人组件中,Mobx已经同时管理局部组件状态和store中的状态,它是简洁、同步、高效、统一的。从经验中,我感受Mobx比React的setState更容易让React初学者理解。Mobx能够咱们保持组件的简洁与简单。

JSBin: 使用setState管理状态

JSBin:使用Mobx管理状态

 

注:一、本文为翻译Michel Weststrat(@mweststrate)的文章,原文地址:https://blog.cloudboost.io/3-reasons-why-i-stopped-using-react-setstate-ab73fc67a42e

二、在我csdn的Mobx系列中有介绍Mobx的使用。

相关文章
相关标签/搜索