react setState 原理


  • 組件的數據來源有兩個地方,分別是屬性對象和狀態對象
  • 屬性是父組件傳遞過來的,不可更改
  • 狀態是自己內部的,改變狀態的唯一方式就是setState
  • 屬性和狀態的變化都會引起視圖更新
import React from "react"; import ReactDOM from "react-dom"; /** * 屬性是由父組件傳遞過來的,不能改變 * 狀態是組件內部,由自己維護,外界無法訪問 改變狀態的唯一方式就是setState */ class Counter extends React.Component{ constructor(props) { //構造函數是唯一給狀態賦值的地方
 super(props) //定義狀態的地方
    this.state = {number: 0} } render(){ //當我們調用setState的時候會引起狀態的改變和組件的更新
    console.log('render') return ( <div>
        <p>{this.state.number}</p>
        <button onClick={() => this.setState({number: this.state.number + 1})}>+</button>
      </div>
 ) } } // let counter = new Counter()
ReactDOM.render(<Counter></Counter>,document.getElementById('root'))

構造函數是唯一定義狀態並且賦值的地方,當我們要改變狀態的值的時候需要通過setState方法,而不是直接修改state的值,並且每次調用setState的時候會引起狀態的改變和組件的更新。

1.不能直接修改state的值

import React from "react"; import ReactDOM from "react-dom"; /** * 屬性是由父組件傳遞過來的,不能改變 * 狀態是組件內部,由自己維護,外界無法訪問 改變狀態的唯一方式就是setState */ class Counter extends React.Component{ constructor(props) { //構造函數是唯一給狀態賦值的地方
 super(props) //定義狀態的地方
    this.state = {number: 0} } add = () => { this.state.number += 1 } render(){ //當我們調用setState的時候會引起狀態的改變和組件的更新
    console.log('render') return ( <div>
        <p>{this.state.number}</p>
        <button onClick={this.add}>+</button>
      </div>
 ) } } // let counter = new Counter()
ReactDOM.render(<Counter></Counter>,document.getElementById('root'))

像這種直接修改state值得方法並不會生效。

 2.state的更新可能是異步

我們知道調用setState會觸發更新操作,這個過程包括更新state,創建新的VNode,在經過diff算法對比差異,決定需要渲染那一部分,假如他是同步更新的話,每次調用都要執行一次前面的流程,這樣會造成很大的性能問題,所以需要將多個setState放進一個隊列里面,、然后再一個一個執行,最后再一次性更新視圖,這樣會提高性能。舉個例子:

let state = {number: 0} function setState(newState) { state = newState console.log(state) } setState({number: state.number + 1}) setState({number: state.number + 2}) setState({number: state.number + 3})

這段代碼會通過 setState 方法改變state值,我們看看打印結果:

 

 可以看到每次調用setState都會改變state的值並且進行渲染,這將是一個非常消耗性能的問題。

所以React針對setState做了一些特別的優化:將多個setState的調用放進了一個隊列,合並成一個來執行,這意味着當調用setState時,state並不會立即更新,看下面這個例子:

let state = {number: 0} let updataQueue = [] function setState(newState) { updataQueue.push(newState) } setState({number: state.number + 1}) setState({number: state.number + 2}) setState({number: state.number + 3}) updataQueue.forEach(item =>{ state = item }) console.log(state) // 3

我們預想的是結果等於6.但是輸出的卻是3,這是因為變成異步更新之后state的值並不會立即更新,所以每次拿到的state都是 0 ,如果我們想要讓結果等於 6,也就是每次都能拿到最新值,那就需要給setState()傳遞一個函數作為參數,在這個函數中可以拿到每次改變后的值,並通過這個函數的返回值得到下一個狀態。

let state = { number: 0 }
let updataQueue = [] //更新函數隊列
let callbackQueue = [] //回調函數隊列
function setState(updataState,callback) { //入隊
 updataQueue.push(updataState) callbackQueue.push(callback) } //清空隊列
function flushUpdata () { for(let i = 0; i < updataQueue.length; i++) { state = updataQueue[i](state) //拿到每次改變后的值作為下一個的狀態 } state = state callbackQueue.forEach(callbackItem => callbackItem()) } function add(){ setState(preState => ({ number: preState.number + 1}),() => { console.log(state) }) setState(preState => ({ number: preState.number + 2}),() => { console.log(state) }) setState(preState => ({ number: preState.number + 3}),() => { console.log(state) }) //批量更新
 flushUpdata() } add() console.log(state) // 6

 

 

 由於回調函數也是異步執行的,所以最后一次性輸出的都是6.

 改寫成class類的形式如下:

class Component { constructor() { this.state = { number: 0 } this.batchUpdata = false
        this.updataQueue = [] //更新隊列
        this.callbackQueue = [] //回調函數隊列
 } setState(updataState, callback) { if (this.batchUpdata) { this.updataQueue.push(updataState) //放入隊列
            this.callbackQueue.push(callback) } } flushUpdata() { let state = this.state // this.updataQueue.forEach(newStateitem => this.state = newStateitem)
        for(let i = 0; i < this.updataQueue.length; i++) { state = this.updataQueue[i](state) } this.state = state this.callbackQueue.forEach(callback => callback()) } add() { this.batchUpdata = true //開啟合並模式

        this.setState(preState => ({ number: preState.number + 1}),() => { console.log(this.state) }) this.setState(preState => ({ number: preState.number + 2}),() => { console.log(this.state) }) this.setState(preState => ({ number: preState.number + 3}),() => { console.log(this.state) }) //批量更新
        this.flushUpdata() } } let c = new Component() c.add() console.log(c.state)

現在這個邏輯對於setState傳入的參數是函數很適合,但是有時候我們希望傳入的是對象,且希望利用setState執行完之后做一些操作,比如在請求到數據之后隱藏進度條等,這個時候就需要setState能變為同步執行,這個時候我們會借助promise、setTimeout等方法來改變setState讓它變為同步的。也就是不用放入隊列,而是立即執行,但是以上邏輯不支持同步的情況,我們需要修改:

class Component { constructor() { this.state = { number: 0 } this.batchUpdata = false
        this.updataQueue = [] //更新隊列
        this.callbackQueue = [] //回調函數隊列
 } setState(updataState, callback) { if (this.batchUpdata) { //批量更新
            this.updataQueue.push(updataState) //放入隊列
            this.callbackQueue.push(callback) }else { //直接更新
            console.log('直接更新') //如果是函數需要把老值傳進去
            if(typeof updataState === 'function') { this.state = updataState(this.state) }else { this.state = updataState } } } flushUpdata() { let state = this.state // console.log(this.updataQueue)
        for(let i = 0; i < this.updataQueue.length; i++) { //為了兼容參數為函數和對象的情況需要判斷一下 參數為對象的時候不用傳上一個的狀態值,參數為函數的時候需要傳上一個的狀態給下一個狀態
            if(typeof this.updataQueue[i] === 'function') { state = this.updataQueue[i](state) }else { state = this.updataQueue[i] } } this.state = state this.callbackQueue.forEach(callback => { if(callback) callback() //為了兼容參數為函數和對象的情況需要判斷一下,參數為對象的時候沒有回調函數就不執行
 }) this.batchUpdata = false //更新完畢置為false
 } add() { this.batchUpdata = true //開啟合並模式
        //不會放進更新隊列
       setTimeout(() => { this.setState({number: this.state.number + 4}) console.log(this.state) },1000) this.setState({number: this.state.number + 1}) // this.setState(preState => ({ number: preState.number + 1}),() => {
    // console.log(this.state)
    // })
    // this.setState({number: this.state.number + 1})

        //批量更新
        this.flushUpdata() } } let c = new Component() c.add() console.log('end'+ JSON.stringify(c.state))

 

批量處理機制就是為了減少setState刷新頁面的次數,setTimeout,promise等異步方法可以直接跳過批量處理機制,setState調幾次就改幾次。

 https://www.cnblogs.com/jiuyi/p/9263114.html這篇文章對於同步更新講的比較好

3.seState的更新會被合並

當調用setState的時候,React會把你要修改的那一部分的對象合並到當前的state上面,舉個栗子

class Counter extends React.Component{ constructor(props) { //構造函數是唯一給狀態賦值的地方
 super(props) this.add = this.add.bind(this) //定義狀態的地方
    this.state = {name: 'leah' ,number: 0} }  add (event) { console.log(event) // this.state.number += 1 不能直接修改state的值
    this.setState({number: this.state.number + 1}) } render(){ console.log(this) //當我們調用setState的時候會引起狀態的改變和組件的更新
    console.log('render') return ( <div>
        <p>{this.state.name}</p>
        <p>{this.state.number}</p>
        <button onClick={this.add}>+</button>
      </div>
 ) } }

當前我們只修改了state.number這個時候,name還是會渲染,我們需要對這部分進行合並

class Component { constructor() { this.state = { name: 'leah', number: 0 } this.batchUpdata = false
        this.updataQueue = [] //更新隊列
        this.callbackQueue = [] //回調函數隊列
 } setState(updataState, callback) { if (this.batchUpdata) { //批量更新
            this.updataQueue.push(updataState) //放入隊列
            this.callbackQueue.push(callback) }else { //直接更新
            console.log('直接更新') //如果是函數需要把老值傳進去
            if(typeof updataState === 'function') { this.state = updataState(this.state) }else { this.state = updataState } } } flushUpdata() { let state = this.state // console.log(this.updataQueue)
        for(let i = 0; i < this.updataQueue.length; i++) { //為了兼容參數為函數和對象的情況需要判斷一下 參數為對象的時候不用傳上一個的狀態值,參數為函數的時候需要傳上一個的狀態給下一個狀態
            let partialState = typeof this.updataQueue[i] === 'function' ? this.updataQueue[i](this.state) : this.updataQueue[i] state = {...state, ...partialState} } this.state = state this.callbackQueue.forEach(callback => { if(callback) callback() //為了兼容參數為函數和對象的情況需要判斷一下,參數為對象的時候沒有回調函數就不執行
 }) this.batchUpdata = false //更新完畢置為false
 } add() { this.batchUpdata = true //開啟合並模式
        //不會放進更新隊列
       setTimeout(() => { this.setState({number: this.state.number + 4}) console.log(this.state) },1000) this.setState({number: this.state.number + 1}) // this.setState(preState => ({ number: preState.number + 1}),() => {
    // console.log(this.state)
    // })
    // this.setState({number: this.state.number + 1})

        //批量更新
        this.flushUpdata() } } let c = new Component() c.add() console.log('end'+ JSON.stringify(c.state))

4.在組件實例中this的指向問題:

一般來說,類的方法里this是undefined,那如何讓普通方法的this指向組件實例呢?

4.1.箭頭函數

class Counter extends React.Component{ constructor(props) { //構造函數是唯一給狀態賦值的地方
 super(props) //定義狀態的地方
    this.state = {number: 0} } add = (event) => { console.log(event) // this.state.number += 1 不能直接修改state的值
    this.setState({number: this.state.number + 1}) this.setState({number: this.state.number + 2}) this.setState({number: this.state.number + 3}) } render(){ console.log(this) //當我們調用setState的時候會引起狀態的改變和組件的更新
    console.log('render') return ( <div>
        <p>{this.state.number}</p>
        <button onClick={this.add}>+</button>
      </div>
 ) } }

4.2.匿名函數

class Counter extends React.Component{ constructor(props) { //構造函數是唯一給狀態賦值的地方
 super(props) //定義狀態的地方
    this.state = {number: 0} } add (event) { console.log(event) // this.state.number += 1 不能直接修改state的值
    this.setState({number: this.state.number + 1}) this.setState({number: this.state.number + 2}) this.setState({number: this.state.number + 3}) } render(){ console.log(this) //當我們調用setState的時候會引起狀態的改變和組件的更新
    console.log('render') return ( <div>
        <p>{this.state.number}</p>
        <button onClick={() => this.add()}>+</button>
      </div>
 ) } }

4.3.bind綁定

class Counter extends React.Component{ constructor(props) { //構造函數是唯一給狀態賦值的地方
 super(props) //定義狀態的地方
    this.state = {number: 0} } /** * 合成事件 react合成事件 * 事件代理 * event 並不是原始的dom對象 而是react二次封裝的事件對象 可以復用 */ add (event) { console.log(event) // this.state.number += 1 不能直接修改state的值
    this.setState({number: this.state.number + 1}) this.setState({number: this.state.number + 2}) this.setState({number: this.state.number + 3}) } render(){ console.log(this) //當我們調用setState的時候會引起狀態的改變和組件的更新
    console.log('render') return ( <div>
        <p>{this.state.number}</p>
        <button onClick={this.add.bind(this)}>+</button>
      </div>
 ) } }

但是這樣綁定有個問題,就是每次渲染的時候都需要綁定一次,所以可以在構造函數里面一次性綁定

class Counter extends React.Component{ constructor(props) { //構造函數是唯一給狀態賦值的地方
 super(props) this.add = this.add.bind(this) //定義狀態的地方
    this.state = {number: 0} } /** * 合成事件 react合成事件 * 事件代理 * event 並不是原始的dom對象 而是react二次封裝的事件對象 可以復用 */ add (event) { console.log(event) // this.state.number += 1 不能直接修改state的值
    this.setState({number: this.state.number + 1}) this.setState({number: this.state.number + 2}) this.setState({number: this.state.number + 3}) } render(){ console.log(this) //當我們調用setState的時候會引起狀態的改變和組件的更新
    console.log('render') return ( <div>
        <p>{this.state.number}</p>
        <button onClick={this.add}>+</button>
      </div>
 ) } }

5.合成事件

合成事件原理:利用事件冒泡機制
如果react事件綁定在了真實DOM節點上,一個節點同事有多個事件時,頁面的響應和內存的占用會受到很大的影響。因此SyntheticEvent作為中間層出現了。
事件沒有在目標對象上綁定,而是在document上監聽所支持的所有事件,當事件發生並冒泡至document時,react將事件內容封裝並叫由真正的處理函數運行。

 

這篇主要講了一下setState的異步更新處理過程。

我們的更新其實並不是真正的異步處理,而是更新的時候把更新內容放到了更新隊列中,最后批次更新,這樣才表現出異步更新的狀態。setTimeout,promise等異步方法可以直接跳過批量處理機制,setState調幾次就改幾次。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM