【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…

相關文章
相關標籤/搜索