在React中,你必定會遇到的setState

只要使用React的小夥伴,必定接觸到setState這個API,地位不言而喻react

但要使用好必定要抓住三個關鍵詞:合併更新、同步異步、不可變值數組

咋一看,有些小夥伴可能雲裏霧裏~性能優化

別急,我挨個解釋markdown

1.setState會合並更新

  • 默認合併更新

先看一個例子🌰網絡

import { Component } from  'react'

class App extends Component {
  state= {
      count: 1
  }
  handleAdd = () => {
      this.setState({
          count: this.state.count + 1
      })
      this.setState({
          count: this.state.count + 1
      })
      this.setState({
          count: this.state.count + 1
      })
      this.setState({
          count: this.state.count + 1
      })
  }
  render() {
    return (
        <div>
          { this.state.count }
          <button onClick={ this.handleAdd }>add</button>
        </div>
    )
  }
}

複製代碼

這段代碼,咱們重複的setState讓count不斷加1異步

可咱們會發現點擊一次按鈕僅加1函數

而不是咱們想一想加4post

其實就是React的底層優化性能

他會把多個setState中的更新的對象合併一次更新優化

底層相似使用 Object.assign 對更新對象進行合併處理

// 等價於 {  count: this.state.count + 1 }
   Object.assign({
      count: this.state.count + 1
    }, {
      count: this.state.count + 1
    }, {
      count: this.state.count + 1
    }, {
      count: this.state.count + 1
    })
複製代碼

那如何避免不會被合併呢?

  • 函數不會被合併

可使用函數,函數不會被合併

// preState 更新以前的 state
// preProps 更新以前的 props
this.setState((preState, preProps) => {
	// return 此次要更新的對象
})
複製代碼

來一個例子🌰

import { Component } from  'react'

class App extends Component {
  state= {
      count: 1
  }
  handleAdd = () => {
      this.setState((preState) => {
          console.log(preState) // {count: 1}
          return ({
              count: preState.count + 1
          })
      })
      this.setState((preState) => {
          console.log(preState) // {count: 2}
          return ({
              count: preState.count + 1
          })
      })
      this.setState((preState) => {
          console.log(preState) // {count: 3}
          return ({
              count: preState.count + 1
          })
      })
      this.setState((preState) => {
          console.log(preState) // {count: 4}
          return ({
              count: preState.count + 1
          })
      })
  }
  render() {
    return (
        <div>
          { this.state.count }
          <button onClick={ this.handleAdd }>add</button>
        </div>
    )
  }
}
複製代碼

2.setState異步同步都是有可能的

  • 默認是異步

這是什麼意思呢?

直接來個例子🌰

import { Component } from  'react'

class App extends Component {
    state= {
        count: 1
    }
    handleAdd = () => {
        this.setState({
            count: this.state.count + 1
        })
        console.log(this.state.count)
    }
    render() {
        return (
            <div>
                { this.state.count }
                <button onClick={ this.handleAdd }>add</button>
            </div>
        )
    }
}
複製代碼

效果圖:

看着效果圖,小夥伴其實能夠一目瞭然發現

console.log 以前其實已經 setState

但獲取的值卻仍是從 1 開始打印,而不是從 2 開始

也就是說 console.log 以前還未 setState

那此時 setState 即是異步更新

但咱們可使用 setState(updater, callback) 第二個參數(回調函數)

相似 Vue 中的 $nextTick

// 代碼同上
// ...
handleAdd = () => {
    this.setState({
        count: this.state.count + 1
    }, () => {
        console.log(this.state.count) // 此時 count 等於2
    })
}
// ...
複製代碼

固然你也能夠在生命週期 componentDidUpdate 拿到所有完成更新的值

componentDidUpdate() {
	console.log(this.state.count) // 此時 count 等於2
}
複製代碼

那什麼時候同步?

  • 同步狀況有:使用異步函數、自定義事件

異步函數能夠是setTimeoutsetIntervalrequestAnimationFrame

class App extends Component {
    state= {
        count: 1
    }

    handleAdd = () => {
        setTimeout(() => {
            this.setState({
                count: this.state.count + 1
            })
            console.log(this.state.count) // 此時 count 等於2
        }, 0)
    }
    render() {
        return (
            <div>
                { this.state.count }
                <button onClick={this.handleAdd}>add</button>
            </div>
        )
    }
}
複製代碼

效果圖:

從代碼和上面的圖,咱們很容易看出

咱們仍是在 console.log 以前 setState

可是打印是從 2 開始的

那就能夠說明此時setState同步更新的

相似自定義事件,附上例子🌰

import { Component } from  'react'

class App extends Component {
    state= {
        count: 1
    }
    componentDidMount() {
    	// 自定義事件
        this.handleAdd = event => {
            this.setState({
                count: this.state.count + 1
            })
            console.log(this.state.count) // 此時 count 等於2
        }
        const button = document.getElementById('button')
        button.addEventListener('click', this.handleAdd);
    }
    componentWillUnmount() {
    	// 移除自定義事件,防止內存泄漏
        const button = document.getElementById('button')
        button.removeEventListener('click', this.handleAdd);
    }

    render() {
        return (
            <div>
                { this.state.count }
                <button id='button'>add</button>
            </div>
        )
    }
}
複製代碼
  • 同步或異步的原理

一張很經典的圖

(圖源於網絡)

這圖什麼意思呢?

其實說白就是 React 用 isBatchingUpdates 去判斷 setState 後該如何處理

咱們把點擊事件拿出舉例🌰

React 會在 setState 以前,isBatchingUpdates = true

到結束時纔會讓,isBatchingUpdates = false

此時,命中圖中左側判斷:保存組件於 dirtyComponents 中

handleAdd = () => {
    // 開始時處於BathingUpdates, isBatchingUpdates = true
    
    this.setState({
        count: this.state.count + 1
    })
    
    console.log(this.state.count) // 此時 count 等於 1
    
    // 結束時 isBatchingUpdates = false
}   
複製代碼

而同步時呢?

一樣,setState 以前,isBatchingUpdates = true

結束時,isBatchingUpdates = false

此時,isBatchingUpdates = false 才 setState 開始工做

命中圖中的右側的判斷,立刻開始一系列的更新數值

handleAdd = () => {
 	// ①開始時處於BathingUpdates, isBatchingUpdates = true
    
    setTimeout(() => {
        this.setState({
            count: this.state.count + 1
        })
        
        console.log(this.state.count) // ③此時 count 等於2
        
    }, 0)
    
   // ②結束時 isBatchingUpdates = false
}
複製代碼

3.setState最好使用不可變值

使用不可變值,其實就是爲了頁面的性能優化

咱們知道其實有些頁面組件沒有更新的必要

例如:父組件更新,子組件也會隨着更新

但子組件其實沒有數值的更新,因此不必更新渲染

咱們可使用 shouldComponentUpdate 手動控制更新渲染

shouldComponentUpdate(nextProps, nextState) {
	//...
    // 若是 return false 表明組件不更新渲染
    // 若是 return true 表明組件更新渲染
}
複製代碼

因此,咱們就要用不可變值,兩個先後不同的props或是state對象進行比較

例如

// ...
// 傳入數組list 
shouldComponentUpdate(nextProps, nextState) {
    // 引入 lodash 的 isEqual 比較數組值是否相等
    if (isEqual(nextProps.list,this.props.list)) {
        return false // 組件不更新
    }
    return false // 組件不更新
}
複製代碼

若是你不使用不可變值,那指向有多是同一個對象

那麼即時你使用shouldComponentUpdate,也會更新渲染

由於他們都是同一個對象

若是小夥伴不太明白~

那就看看我以前寫的文章 如何理解react中的setState必定要用不可變值?說的很詳細~

感謝閱讀~

相關文章
相關標籤/搜索