[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的使用。

相關文章
相關標籤/搜索