this.state和this.props的更新多是異步的,React可能會出於性能考慮,將多個setState的調用,合併到一次State的更新中。
this.state的值計算下一個狀態。引用官網的一個代碼示例:
// Wrong
this.setState({
counter: this.state.counter + this.props.increment,
});
若是必定要這麼作,可使用另外一個以函數做爲參數的setState方法,這個函數的第一個參數是前一個State,第二個參數是當前接收到的最新Props。以下所示:
// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
在調用setState以後,也不能當即使用this.state獲取最新狀態,由於這時的state極可能尚未被更新,要想保證獲取到的state是最新的state,能夠在componentDidUpdate中獲取this.state。也可使用帶用回調函數參數版本的setStatesetState(stateChange, [callback]),回調函數中的this.state會保證是最新的state。
複製代碼
0x00
The SyntheticEventis pooled. This means that the SyntheticEventobject will be reused and all properties will be nullified after the event callback has been invoked. This is for performance reasons. As such, you cannot access the event in an asynchronous way.在 React 事件處理中,事件對象被包裝在一個 SyntheticEvent(合成事件)對象中。這些對象是被池化的(pooled),這意味着在事件處理程序會把這些對象重用於其餘事件以提升性能。隨之而來的問題就是,異步訪問事件對象的屬性是不可能的,由於事件的屬性因爲重用而被重置(nullified)。
0x01
下面代碼存在問題:
function handleClick(event) {
setTimeout(function () {
console.log(event.target.name);
}, 1000);
}
控制檯會輸出 null,由於每次事件回調完成後,SyntheticEvent 會被重置。
解決方式是把 event 賦值到一個內部變量上。
function handleClick(event) {
let name = event.target.name; // 內部變量保存 event.target.name 的值
setTimeout(function () {
console.log(name);
}, 1000);
}
0x02
facebook 官方的實例:
function onClick(event) {
console.log(event); // => nullified object.
console.log(event.type); // => "click"
const eventType = event.type; // => "click"
setTimeout(function() {
console.log(event.type); // => null
console.log(eventType); // => "click"
}, 0);
// Won't work. this.state.clickEvent will only contain null values. this.setState({clickEvent: event}); // You can still export event properties. this.setState({eventType: event.type}); } 若是想異步訪問事件屬性,能夠在事件上調用 event.persist(),這會從池中移除合成事件並容許對事件的引用被保留。 複製代碼
0x00
React has a setState() problem: Asking newbies to use setState() is a recipe for headaches. Advanced users have secret cures.爲了性能和其它緣由,setState 這個 API 很容易被誤用。
setState 不會馬上改變 React 組件中 state 的值
setState 經過引起一次組件的更新過程來引起從新繪製
屢次 setState 函數調用產生的效果會合並
0x01
問題
看以下代碼:
// state.count 當前爲 0
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
// state.count 如今是 1,而不是 3
三次操做被合併爲了一次。
0x02
解決方式
this.setState((prevState, props) => ({
count: prevState.count + props.increment
}));
要修復它,請使用第二種形式的 setState() 來接受一個函數而不是一個對象。 該函數將接收先前的狀態做爲第一個參數,將須要更新的值做爲第二個參數:
// Correct
this.setState((prevState, props) => ({
counter: prevState.counter + props.increment
}));
上方代碼使用了箭頭函數,但它也適用於常規函數:
// Correct
this.setState(function(prevState, props) {
return {
counter: prevState.counter + props.increment
};
});
複製代碼
setState(nextState, callback)
複製代碼
這是UI更新最經常使用的方法,合併新的state到現有的state。javascript
nextState
能夠爲一個對象,包含0個或多個要更新的key。最簡單的用法爲:html
this.setState({
key1: value1,
key2: value2
});
複製代碼
這種方式能應付大部分的應用場景,可是看看下面這種狀況:前端
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
複製代碼
最後獲得的count倒是不可控的。由於setState
不會當即改變this.state
,而是掛起狀態轉換,調用setState
方法後當即訪問this.state
可能獲得的是舊的值。java
setState
方法不會阻塞state更新完畢react
第二個setState
可能還沒等待第一次的state更新完畢就開始執行了,因此最後count可能只加了1。git
這時setState
的第二個參數就派上用場了,第二個參數是state更新完畢的回調函數es6
this.setState({
count: this.state.count + 1
}, () => {
this.setState({
count: this.state.count + 1
});
});
複製代碼
不過看起來很怪,es6
中可使用Promise
更優雅的使用這個函數,封裝一下setState
github
function setStateAsync(nextState){
return new Promise(resolve => {
this.setState(nextState, resolve);
});
}
複製代碼
上面的例子就能夠這樣寫編程
async func() {
...
await this.setStateAsync({count: this.state.count + 1});
await this.setStateAsync({count: this.state.count + 1});
}
複製代碼
順眼多了。redux
nextState
也能夠是一個function
,稱爲狀態計算函數,結構爲function(state, props) => newState
。這個函數會將每次更新加入隊列中,執行時經過當前的state
和props
來獲取新的state
。那麼上面的例子就能夠這樣寫
this.setState((state, props) => {
return {count: state.count + 1};
});
this.setState((state, props) => {
return {count: state.count + 1};
});
複製代碼
每次更新時都會提取出當前的state,進行運算獲得新的state,就保證了數據的同步更新。
默認調用setState
都會從新渲染視圖,可是經過shouldComponentUpdate()
函數返回false
來避免從新渲染。
若是可變對象沒法在shouldComponentUpdate()
函數中實現條件渲染,則須要控制newState
與prevState
不一樣時才調用setState
來避免沒必要要的從新渲染。
這個問題能夠有兩個答案:
setState
實現起來就會困難重重。React 的設計初衷本是簡化應用開發流程,可是:
setState()
的使用時機和方式(由於你有些 state 可能尚未渲染到屏幕上)。在這幾種狀況下,困惑都來源於 React 組件生命週期的限制性(這些限制是刻意設計的,是好的)。
從屬 State(Dependent State)
更新 state 時,更新結果可能依賴於:
當存在這幾種從屬 state 的時候,若是你還想簡單直接地更新 state,那 React 的表現行爲會讓你大吃一驚,而且是以一種使人憎惡又難以調試的方式。大多數狀況下,你的代碼根本沒法工做:要麼 state 不對,要麼控制檯有錯誤。
我之因此吐槽 setState()
,是由於它的這種限制性在 API 文檔中並無詳細說明,關於應對這種限制性的各類通用模式也未能闡述清楚。這迫使初學者只能不斷試錯、Google 或者從其餘社區成員那裏尋求幫助,但實際上在文檔中本該就有更好的新手指南。
當前關於 setState()
的文檔開頭以下:
setState(nextState, callback)複製代碼
將 nextState 淺合併到當前 state。這是在事件處理函數和服務器請求回調函數中觸發 UI 更新的主要方法。
在末尾確實也提到了其異步行爲:
不保證
setState
調用會同步執行,考慮到性能問題,可能會對屢次調用做批處理。
這就是不少用戶層(userland) bug 的根本緣由:
// 假設 state.count === 0
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
// state.count === 1, 而不是 3複製代碼
本質上等同於:
Object.assign(state,
{count: state.count + 1},
{count: state.count + 1},
{count: state.count + 1}
); // {count: 1}複製代碼
這在文檔中並未顯式說明(在另一份特殊指南中提到了)。
文檔還提到了另一種函數式的 setState()
語法:
也能夠傳遞一個簽名爲
function(state, props) => newState
的函數做爲參數。這會將一個原子性的更新操做加入更新隊列,在設置任何值以前,此操做會查詢前一刻的 state 和 props。
...
setState()
並不會當即改變this.state
,而是會建立一個待執行的變更。調用此方法後訪問this.state
有可能會獲得當前已存在的 state(譯註:指 state 還沒有來得及改變)。
API 文檔雖提供了些許線索,但未能以一種清晰明瞭的方式闡明初學者常常遇到的怪異表現。開發模式下,儘管 React 的錯誤信息以有效、準確著稱,但當 setState()
的同步問題出現 bug 的時候控制檯卻沒有任何警告。
StackOverflow 上有關 setState()
的問題大都要歸結於組件的生命週期問題。毫無疑問,React 很是流行,所以那些問題都被問爛了,也有着各類參差不齊的回答。
那麼,初學者究竟該如何掌握 setState()
呢?
在 React 的文檔中還有一份名爲 「 state 和生命週期」的指南,該指南提供了更多深刻內容:
「…要解決此問題,請使用
setState()
的第二種形式 —— 以一個函數而不是對象做爲參數,此函數的第一個參數是前一刻的 state,第二個參數是 state 更新執行瞬間的 props :」
// 正確用法
this.setState((prevState, props) => ({
count: prevState.count + props.increment
}));複製代碼
這個函數參數形式(有時被稱爲「函數式 setState()
」)的工做機制更像:
[
{increment: 1},
{increment: 1},
{increment: 1}
].reduce((prevState, props) => ({
count: prevState.count + props.increment
}), {count: 0}); // {count: 3}複製代碼
不明白 reduce 的工做機制? 參見 「Composing Software」 的 「Reduce」 教程。
關鍵點在於更新函數(updater function):
(prevState, props) => ({
count: prevState.count + props.increment
})複製代碼
這基本上就是個 reducer,其中 prevState
相似於一個累加器(accumulator),而 props
則像是新的數據源。相似於 Redux 中的 reducers,你可使用任何標準的 reduce 工具庫對該函數進行 reduce(包括 Array.prototype.reduce()
)。一樣相似於 Redux,reducer 應該是 純函數 。
注意:企圖直接修改
prevState
一般都是初學者困惑的根源。
API 文檔中並未說起更新函數的這些特性和要求,因此,即便少數幸運的初學者碰巧了解到函數式 setState()
能夠實現一些對象字面量形式沒法實現的功能,最終依然可能困惑不解。
僅僅是新手纔有的問題嗎?
直到如今,在處理表單或是 DOM 元素座標位置的時候,我仍是會時不時得掉到坑裏去。當你使用 setState()
的時候,你必須直接面對組件生命週期的相關問題;但當你使用容器組件或是經過 props 來存儲和傳遞 state 的時候,React 則會替你處理同步問題。
不管你有經驗與否 ,處理共享的可變 state 和 state 鎖(state locks)都是很棘手的。經驗豐富之人只不過是能更加快速地定位問題,而後找出一個巧妙的變通方案罷了。
由於初學者從未遇到過這種問題,更不知規避方案,因此是掉坑裏摔得最慘的。
當問題發生時,你固然能夠選擇和 React 鬥個你死我活;不過,你也能夠選擇讓 React 順其天然的工做。這就是我說即便是對初學者而言,Redux 有時 都比 setState
更簡單的緣由。
在併發系統中,state 更新一般按其中一種方式進行:
setState()
)(譯註:即常見的鎖機制)在我看來(在向不少學生教授過這兩種方法以後),相比於第二種方法,第一種方法更加容易致使錯誤,也更加容易使人困惑。當 state 更新被簡單地阻塞時(在 setState
的例子中,也能夠叫批處理化或延遲執行),解決問題的正確方法並不十分清晰明瞭。
當遇到 setState()
的同步問題時,個人直覺反應實際上是很簡單的:將 state 的管理上移到 Redux(或 MobX) 或容器組件中。基於多方面緣由 ,我本身使用同時也推薦他人使用 Redux,但很顯然,這並非一條放之四海而皆準的建議。
Redux 自有其陡峭的學習曲線,但它規避了共享的可變 state 以及 state 更新同步等複雜問題。所以我發現,一旦我教會了學生如何避免可變性,接下來基本就一路順風了。
對於沒有任何函數式編程經驗的新手而言,學習 Redux 遇到的問題可能會比學習 setState()
遇到的更多 —— 可是,Redux 至少有不少其做者親自講授的免費 教程
React 應當向 Redux 學習:有關 React 編程模式和 setState()
踩坑的視頻教程定能讓 React 主頁錦上添花。
在渲染以前決定 State
將 state 管理移到容器組件(或 Redux)中能促使你從另外一個角度思考組件 state 問題,由於這種狀況下,在組件渲染以前,其 state 必須是既定的(由於你必須將其做爲 props 傳下去)。
重要的事情說三遍:
渲染以前,決定 state!
渲染以前,決定 state!
渲染以前,決定 state!
說完三篇以後就能夠獲得一個顯然的推論:在 render()
函數中調用 setState()
是反模式的。
在 render
函數中計算從屬 state 是 OK 的(好比說, state 中有 firstName
和 lastName
,據此你計算出 fullName
,在 render
函數中這樣作徹底是 OK 的),但我仍是傾向於在容器組件中計算出從屬 state ,而後經過 props 將其傳遞給展現組件(presentation components)。
setState() 該怎麼治?
我傾向於廢棄掉對象字面量形式的 setState()
,我知道這(表面上看)更加易於理解也更加方便(譯者:「這」指對象字面量形式的 setState()
),但它也是坑之所在啊。用腳指頭都能猜到,確定有人這樣寫:
state.count; // 0
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});
this.setState({count: state.count + 1});複製代碼
而後天真就地覺得 {count: 3}
。批量化處理後對象的同名 props 被合併掉的狀況幾乎不多是用戶所指望的行爲,反正我是沒見過這種例子。要是真存在這種狀況,那我必須說這跟 React 的實現細節耦合地太緊密了,根本不能做爲有效參考用例。
我也但願 API 文檔中有關 setState()
的章節可以加上「 state 和聲明週期」這一深度指南的連接,這能給那些想要全面學習 setState()
的用戶更多的細節內容。setState()
並不是同步操做,也無任何有意義的返回結果,僅僅是簡單地描述其函數簽名而沒有深刻地探討其各類影響和表現,這對初學者是極不友好的。
初學者必須花上大量時間去找出問題:Google 上搜、StackOverflow 上搜、GitHub issues 裏搜。
setState() 爲什麼如此嚴苛?
setState() 的怪異表現並不是 bug,而是特性。實際上,甚至能夠說這是 React 之因此存在的根本緣由。
React 的一大創做動機就是保證肯定性渲染:給定應用 state ,渲染出特定結果。理想狀況下,給定 state 相同,渲染結果也應相同。
爲了達到此目的,當發生變化時,React 經過採起一些限制性手段來管理變化。咱們不能隨意取得某些 DOM 節點而後就地修改之。相反,React 負責 DOM 渲染;當 state 發生改變時,也由React 決定如何重繪。咱們不渲染 DOM,而是由 React 來負責。
爲了不在 state 更新的過程當中觸發重繪,React 引入了一條規則:
React 用於渲染的 state 不能在 DOM 渲染的過程當中發生改變。咱們不能決定組件 state 什麼時候獲得更新,而是由 React 來決定。
困惑就此而來。當你調用 setState()
時,你覺得你設置了 state ,其實並無。
「你就接着裝逼,你覺得你因此爲的就是你因此爲的嗎?」
什麼時候使用 setState()?
我通常只在不須要持久化 state 的自包含功能單元中使用 setState()
,例如可複用的表單校驗組件、自定義的日期或時間選擇部件(widget)、可自定義界面的數據可視化部件等。
我稱這種組件爲「小部件(widget)」,它們通常由兩個或兩個以上組件構成:一個負責內部 state 管理的容器組件,一個或多個負責界面顯示的子組件
幾條立見分曉的檢驗方法(litmus tests):
若是這兩個問題的答案都是「否」的話,那使用 setState()
基本是沒問題的;不然,就要另做考慮了。
據我所知,Facebook 使用受管於 Relay container 的 setState()
來包裝 Facebook UI 的各個不一樣部分,例如大型 Facebook 應用內部的迷你型應用。於 Facebook 而言,以這種方式將複雜的數據依賴和須要實際使用這些數據的組件放在一塊兒是很好的。
對於大型(企業級)應用,我也推薦這種策略。若是你的應用代碼量很是大(十萬行以上),那此策略多是很好的 —— 但這並不意味着這種方式就不能應用於小型應用中。
相似地,並不意味着你不能將大型應用拆分紅多個獨立的迷你型應用。我本身就結合 Redux爲企業級應用這樣作過。例如,我常常將分析面板、消息管理、系統管理、團隊/成員角色管理以及帳單管理等模塊拆分紅多個獨立的應用,每一個應用都有其本身的 Redux store。經過 API tokens 和 OAuth,這些應用共享同一個域下的登陸/session 管理,感受就像是一個統一的應用。
對於大多數應用,我建議默認使用 Redux。須要指出的是,Dan Abramov(Redux 的做者)在這一點上和我持相反的觀點。他喜歡應用盡量地保持簡單,這固然沒錯。傳統社區有句格言如是說:「除非真得感到痛苦,不然就別用 Redux」。
而個人觀點是:
「不知道本身正走在黑暗中的人是永遠不會去搜尋光明的「。
正如我說過的,在某些狀況下,Redux 比 setState()
更簡單。經過消除一切和共享的可變 state 以及同步依賴有關的 bug,Redux 簡化了 state 管理問題。
setState()
確定要學,但即便你不想使用 Redux,你也應該學學 Redux。不管你採用何種解決方案,它都能讓你重新的角度思考去應用的 state 管理問題,也可能能幫你簡化應用 state。
對於有大量衍生(derived ) state 的應用而言, MobX 可能會比 setState()
和 Redux 都要好,由於它很是擅於高效地管理和組織須要經過計算獲得的(calculated ) state 。
得利於其細粒度的、可觀察的訂閱模型,MobX也很擅於高效渲染大量(數以萬計)動態 DOM 節點。所以,若是你正在開發的是一款圖形遊戲,或者是一個監控全部企業級微服務實例的控制檯,那 MobX 多是個很好的選擇,它很是有利於實時地可視化展現這種複雜的信息。
接下來
想要全面學習如何用 React 和 Redux 開發軟件?
跟着 Eric Elliott 學 Javacript,機不可失時再也不來!
Eric Elliott 是 「編寫 JavaScript 應用」 (O’Reilly) 以及 「跟着 Eric Elliott 學 Javascript」 兩書的做者。他爲許多公司和組織做過貢獻,例如 Adobe Systems、Zumba Fitness、The Wall Street Journal、ESPN和BBC等 , 也是不少機構的頂級藝術家,包括但不限於 Usher , Frank Ocean , Metallica。
大多數時間,他都在 San Francisco Bay Area,同這世上最美的女子在一塊兒(譯註:這是怕老婆呢仍是怕老婆呢仍是怕老婆呢?)。
React 文檔 最近改版了——若是你還沒看過,你的確應該去看看!經過寫一份「React 術語詞典」我愈來愈有豁然開朗的感受了,其過程當中我也深刻地通讀了新文檔的所有內容。閱讀文檔的時候,我發現了 setState
相對鮮爲人知的一面,並由這個推文大受啓發:
我想我要寫一篇博文來解釋其原理。
先介紹一下背景
React 中的組件是獨立、可重用的代碼塊,它們常常有本身的狀態。組件返回的 React 元素組成了應用的 UI 界面。含有本地狀態的組件會有一個名爲 state
的屬性,當咱們想要改變應用的外觀或表現形式時,咱們須要改變組件的狀態。那麼咱們如何更新組件的狀態呢?React 組件中有一個可用的方法叫作 setState
,它經過調用 this.setState
來使得 React 從新渲染你的應用並更新 DOM。
一般更新組件的時候,咱們只要調用 setState
函數並以對象的形式傳入一個新的值:this.setState({someField:someValue})
。
可是常常會須要使用當前狀態去更新組件的狀態,直接訪問 this.state
來更新組件到下一個狀態並是不可靠的方式。根據 React 的文檔:
由於
this.props
和this.state
存在異步更新的可能,你不該該根據這些值計算下一個狀態。
文檔中的關鍵詞是異步!當調用 this.setState
時,DOM 並不能立刻更新,React 會分批次地更新,這樣才能更高效地從新渲染全部的組件。
示例
咱們來看一下在 Shopsifter 中使用 setState
的典型例子(我用於收集反饋信息),在用戶提交他/她的反饋信息以後,頁面會顯示感謝信息以下:
反饋頁面的組件擁有一個布爾值的 showForm
屬性,該值決定了應該顯示錶單仍是感謝信息。個人反饋表單組件的初始化狀態是這樣的:
this.state = { showForm : true}
複製代碼
而後,當用戶點擊了提交按鈕,我調用了這個函數:
submit(){
this.setState({showForm : !this.state.showForm});
}
複製代碼
我依賴於 this.state.showForm
的值來改變表單的下一個狀態。這個簡單的例子中,依賴這個值可能並不會致使任何問題,可是想象一下,當一個應用變得更加複雜,會有不少次調用 setState
並依次將數據渲染至 DOM ,可能 this.state.showForm
的實際狀態並非你所認爲的樣子。
若是咱們不依賴於 this.state
來計算下一個值,咱們該怎樣作呢?
setState
中的函數來拯救你了!
咱們能夠向 this.setState
傳入一個函數來替代傳入對象,而且能夠可靠地獲取組件的當前狀態。上文的提交函數如今是這樣寫的:
submit(){
this.setState(function(prevState, props){
return {showForm: !prevState.showForm}
});
}
複製代碼
經過使用函數替代對象傳入 setState
的方式可以獲得組件的 state
和 props
屬性可靠的值。值得注意的一點是,在 React 文檔的例子中使用了箭頭函數(這也是我將要應用到個人 Shopsifter 應用中的一項內容),所以上文的例子中個人函數使用的仍然是 ES5 的語法。
若是你知道本身將要使用 setState
來更新組件,而且你知道本身將要使用當前組件的狀態或者屬性值來計算下一個狀態,我推薦你傳入一個函數做爲 this.setState
的第一個參數而不用對象的解決方案。
我但願這能幫助你作出更好、更可靠的 React 應用!
原文發表在個人博客:www.erichain.me/2017/04/17/…
React 是我作前端以來接觸到的第三個框架(前兩個分別是 Angular 和 Vue),不管是從開發體驗上和效率上,這都是一門很是優秀的框架,很是值得學習。
原諒我說了一些廢話,如下是正文。
藉助於 Redux,咱們能夠輕鬆的對 React 中的狀態進行管理和維護,同時,React 也爲咱們提供了組件內的狀態管理的方案,也就是 setState()
。本文不會涉及到 Redux,咱們將從 Component 的角度來講明你不知道的以及更合理的 setState()
。
先說說你們都知道的
在 React 文檔的 State and Lifecycle 一章中,其實有明確的說明 setState()
的用法,向 setState()
中傳入一個對象來對已有的 state 進行更新。
好比如今有下面的這樣一段代碼:
class MyComponent extends React.Component {
constructor(props) {
super(props);
this.state = {
count: this.state.count + 1
};
}
}複製代碼
咱們若是想要對這個 state 進行更新的話,就能夠這樣使用 setState()
:
this.setState({
count: 1
});複製代碼
你可能不知道的
最基本的用法世人皆知,可是,在 React 的文檔下面,還寫着,處理關於異步更新 state 的問題的時候,就不能簡單地傳入對象來進行更新了。這個時候,須要採用另一種方式來對 state 進行更新。
setState()
不只可以接受一個對象做爲參數,還可以接受一個函數做爲參數。函數的參數即爲 state 的前一個狀態以及 props。
因此,咱們能夠向下面這樣來更新 state:
this.setState((prevState, props) => ({ count: prevState.count + 1 }));複製代碼
這樣寫的話,可以達到一樣的效果。那麼,他們之間有什麼區別呢?
區別
咱們來詳細探討一下爲何會有兩種設置 state 的方案,他們之間有什麼區別,咱們應該在什麼時候使用何種方案來更新咱們的 state 纔是最好的。
此處,爲了可以明確的看出 state 的更新,咱們採用一個比較簡單的例子來進行說明。
咱們設置一個累加器,在 state 上設置一個 count
屬性,同時,爲其增長一個 increment
方法,經過這個 increment
方法來更新 count
。
此處,咱們採用給 setState()
傳入對象的方式來更新 state,同時,咱們在此處設置每調用一次 increment
方法的時候,就調用兩次 setState()
。具體的緣由咱們在後文中會講解。
具體的代碼以下:
class IncrementByObject extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increment = this.increment.bind(this);
}
// 此處設置調用兩次 setState()
increment() {
this.setState({
count: this.state.count + 1
});
this.setState({
count: this.state.count + 1
});
}
render() {
return (
<div>
<button onClick={this.increment}>IncrementByObject</button>
<span>{this.state.count}</span>
</div>
);
}
}
ReactDOM.render(
<IncrementByObject />,
document.getElementById('root')
);複製代碼
這時候,咱們點擊 button 的時候,count
就會更新了。可是,可能與咱們所預期的有所差異。咱們設置了點擊一次就調用兩次 setState()
,可是,count
每一次卻仍是隻增長了 1,因此這是爲何呢?
其實,在 React 內部,對於這種狀況,採用的是對象合併的操做,就和咱們所熟知的 Object.assign()
執行的結果同樣。
好比,咱們有如下的代碼:
Object.assign({}, { a: 2, b: 3 }, { a: 1, c: 4 });複製代碼
那麼,咱們最終獲得的結果將會是 { a: 1, b: 3, c: 4 }
。對象合併的操做,屬性值將會以最後設置的屬性的值爲準,若是發現以前存在相同的屬性,那麼,這個屬性將會被後設置的屬性所替換。因此,也就不難理解爲何咱們調用了兩次 setState()
以後,count
依然只增長了 1 了。
用簡短的代碼說明就是這樣:
this.setState({
count: this.state.count + 1
});
// 同理於
Object.assign({}, this.state, { count: this.state.count + 1 });複製代碼
以上是咱們採用對象的方式傳入 setState()
來更新 state 的說明。接下來咱們再看看使用函數的方式來更新 state 會有怎麼樣的效果呢?
咱們將上面的累加器採用另外的方式來實現一次,在 setState()
的時候,咱們採用傳入一個函數的方式來更新咱們的 state。
class IncrementByFunction extends React.Component {
constructor(props) {
super(props);
this.state = {
count: 0
};
this.increment = this.increment.bind(this);
}
increment() {
// 採用傳入函數的方式來更新 state
this.setState((prevState, props) => ({
count: prevState.count + 1
}));
this.setState((prevState, props) => ({
count: prevState.count + 1
}));
}
render() {
return (
<div>
<button onClick={this.increment}>IncrementByFunction</button>
<span>{this.state.count}</span>
</div>
);
}
}
ReactDOM.render(
<IncrementByFunction />,
document.getElementById('root')
);複製代碼
當咱們再次點擊按鈕的時候,就會發現,咱們的累加器就會每次增長 2 了。
咱們能夠經過查看 React 的源代碼來找出這兩種更新 state 的區別 (此處只展現經過傳入函數進行更新的方式的部分源碼)。
在 React 的源代碼中,咱們能夠看到這樣一句代碼:
this.updater.enqueueSetState(this, partialState, callback, 'setState');複製代碼
而後,enqueueSetState
函數中又會有這樣的實現:
queue.push(partialState);
enqueueUpdate(internalInstance);複製代碼
因此,與傳入對象更新 state 的方式不一樣,咱們傳入函數來更新 state 的時候,React 會把咱們更新 state 的函數加入到一個隊列裏面,而後,按照函數的順序依次調用。同時,爲每一個函數傳入 state 的前一個狀態,這樣,就能更合理的來更新咱們的 state 了。
問題所在
那麼,這就是傳入對象來更新 state 會致使的問題嗎?固然,這只是問題之一,還不是主要的問題。
咱們以前也說過,咱們在處理異步更新的時候,須要用到傳入函數的方式來更新咱們的 state。這樣,在更新下一個 state 的時候,咱們可以正確的獲取到以前的 state,並在在其基礎之上進行相應的修改。而不是簡單地執行所謂的對象合併。
因此說,咱們建議,在使用 setState
的時候,採用傳入函數來更新 state 的方式,這樣也是一個更合理的方式。