您好,登錄后才能下訂單哦!
這篇文章主要介紹“React中setState的更新機制是什么”,在日常操作中,相信很多人在React中setState的更新機制是什么問題上存在疑惑,小編查閱了各式資料,整理出簡單好用的操作方法,希望對大家解答”React中setState的更新機制是什么”的疑惑有所幫助!接下來,請跟著小編一起來學習吧!
setState作為react中的重要部分,將對組件 state 的更改排入隊列,并通知 React 需要使用更新后的 state 重新渲染此組件及其子組件。
state
是React
中的重要概念。我們知道,React
是通過狀態管理來實現對組件的管理。那么,React
是如何控制組件的狀態的,又是如何利用狀態來管理組件的呢?【相關推薦:Redis視頻教程】
我們都知道,React
通過this.state
來訪問state
,通過this.setState()
方法更新state
。當this.setState()
被調用的時候,React
會重新調用render
方法來重新渲染UI
。
setState
已經是我們非常熟悉的一個API
,然而你真的了解它嗎?下面我們將一起來解密setState
的更新機制。
大家剛開始寫React
的時候,通常會寫出 this.state.value = 1
這樣的代碼,這是完全錯誤的寫法。
注意:絕對不要直接修改 this.state,這不僅是一種低效的做法,而且很有可能會被之后的操作替換。
setState
通過一個隊列機制實現state
更新。當執行setState
時,會將需要更新的state
合并后放入狀態對列,而不會立刻更新this.state
,隊列機制可以高效地批量更新state
。如果不通過setState
而直接修改this.state
的值,那么該state
將不會被放入狀態隊列中,當下次調用setState
并對狀態隊列進行合并時,將會忽略之前直接被修改的 state
,而造成無法預知的錯誤。
因此,應該使用 setState
方法來更新 state
,同時 React
也正是利用狀態隊列機制實現了 setState
的異步更新,避免頻繁地重復更新 state
。相關代碼如下:
// 將新的state合并到狀態更新隊列中 var nextState = this._processPendingState(nextProps, nextContext); // 根據更新隊列和 shouldComponentUpdate 的狀態來判斷是否需要更新組件 var shouldUpdate = this._pendingForceUpdte || !inst.shouldCompoonentUpdate || inst.shouldComponentUpdate(nextProps, nextState, nextContext0;
當調用setState
時,實際上會執行 enqueueSetState
方法,并對 partialState
以及 _pendingStateQueue
更新隊列進行合并操作,最終操作 enqueueSetState
執行 state
更新。
而 performUpdateIfNecessary
方法會獲取 _pendingElement、_pendingStateQueue、_pendingForceUpdate
,并調用 receiveComponent
和 updateComponent
方法進行組件更新。
如果在 shouldComponetUpdate
或 componentWillUpdate
方法中調用 setState
, 此時 this._pendingStateQueue != null
, 則 performUpateIfNecessary
方法就會調用 updateComponent
方法進行組件更新,但 updateComponent
方法又會調用 shouldComponentUpdate
和 componentWillUpdate
方法,因此造成循環調用,使得瀏覽器內存占滿后崩潰。
既然 setState
最終是通過 enqueueUpate
執行 state
更新,那么 enqueueUpdate
到底是如何更新 state
的呢?
首先,看看下面這個問題,你是否能夠正確回答呢?
import React, { Component } from 'react' class Example extends Component { constructor() { super() this.state = { val: 0 } } componentDidMount() { this.setState({val: this.state.val + 1}) console.log(this.state.val) this.setState({val: this.state.val + 1}) console.log(this.state.val) setTimeout(() => { this.setState({val: this.state.val + 1}) console.log(this.state.val) this.setState({val: this.state.val + 1}) console.log(this.state.val) },0) } render() { return null } }
上述代碼中, 4 次 console.log
打印出來的 val
分別是:0、0、2、3
。
假如結果與你心中的答案不完全相同,那么你是否想知道 enqueueUpdate
到底做了什么?
下圖是一個簡化的 setState
調用棧,注意其中核心的狀態判斷。
setState
簡化調用棧
到底是怎么導致 setState
的各種不同表現的呢?
我們先要了解事務跟 setState
的不同表現有什么關系。首先,我們把4次 setState
簡單歸類,前兩次屬于一類,因為他們在同一次調用棧中執行,setTimeout
中的兩次 setState
屬于另一類,因為他們也是在同一次調用棧中執行。我們分析一下這兩類 setState
的調用棧。
在 componentDidMount
中直接調用的兩次 setState
,其調用棧更加復雜;而setTimeout
中調用的兩次 setState
,其調用棧則簡單很多。下面我們重點看看第一類 setState
的調用棧,我們發現了 batchedUpdates
方法,原來早在 setState
調用前,已經處于batchedUpdates
執行的事務中了。
那batchedUpdates
方法,又是誰調用的呢?我們再往前追溯一層,原來是 ReactMount.js 中的 _renderNewRootComponent
方法。也就是說,整個將React組件渲染到DOM中的過程就處于一個大的事務中。
接下來的解釋就順理成章了,因為在componentDidMount
中調用setState
時,batchingStrategy
的 isBatchingUpdates
已經被設為true
,所以兩次setState
的結果并沒有立即生效,而是被放到了dirtyComponents
中。這也解釋了兩次打印 this.state.val
都是 0
的原因,因為新的 state
還沒有被應用到組件中。
componentDidMount
中setState
的調用棧
setTimeout
中setState
的調用棧
再反觀 setTimeout
中的兩次setState
,因為沒有前置的 batchedUpdate
調用,所以 batchingStrategy
的 isBatchingUpates
標志位是false
,也就導致了新的 state
馬上生效,沒有走到 dirtyComponents
分支。也就是說,setTimeout
中第一次執行 setState
時,this.state.val
為 1
, 而 setState
完成打印后打印時 this.state.val
變成了2
。第二次的 setState
同理。
前面介紹事務時,也提到了其在 React
源碼中的多處應用,像 initialize、perform、close、closeAll、motifyAll
等方法出現在調用棧中,都說明當前處于一個事務中。
既然事務這么有用,我們寫應用代碼時能使用它嗎?很可惜,答案是不能。盡管React
不建議我們直接使用事務,但在 React 15.0
之前的版本中還是為開發者提供了 batchedUpdates
方法,它可以解決針對一開始例子中setTimeout
里的兩次 setState
導致兩次 render
的情況:
import ReactDOM, { unstable_batchedUpates } from 'teact-dom' unstable_batchedUpates(() => { this.setState(val: this.state.val + 1) this.setState(val: this.state.val + 1) })
在 React 15.0
以及之后版本中,已經徹底將 batchUpdates
這個 API
移除了,因此不再建議開發者使用它。
到此,關于“React中setState的更新機制是什么”的學習就結束了,希望能夠解決大家的疑惑。理論與實踐的搭配能更好的幫助大家學習,快去試試吧!若想繼續學習更多相關知識,請繼續關注億速云網站,小編會繼續努力為大家帶來更多實用的文章!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。