本文翻譯自:Functional setState is the future of React – freeCodeCamp.orgreact
譯者注:昨天本身有遇到一個 setState 的坑,就是【React 踩坑記】setState 這樣用可能會出錯!這篇文章裏記錄的,上網 Google 了下看到這一篇,關於 setState,這篇文章講解的很詳細深刻👍,因此翻譯到掘金來,讓更多人能夠看到。git
更新:我在React Rally上就此主題進行了後續討論。雖然這篇文章更多的是關於「函數式的 setState」模式,但更多的是關於深刻理解setState。github
我在React Rally 上關於 setState 作的一個分享 Justice Mba - Demystifying setState() - YouTubeweb
React在JavaScript中推廣了函數式編程,這致使了大量的框架採用了React使用的基於組件的UI模式。現在,函數式熱潮正在蔓延到整個 web 開發生態系統中。編程
譯者注:上述內容翻譯以下:數組
JavaScript生態系統正在從「本週的新框架」轉變爲「新的(更快的)本週的React克隆」安全
ReactJS新聞@ReactJSNews
阿里巴巴發佈了他們本身的類React框架,彷佛是更輕量更快的 - 可是確定有一個缺點!github.com/alibaba/rax框架
但React團隊遠沒有放鬆。他們繼續深刻挖掘,探索更多的函數式寶石。異步
因此今天我向你透露一個隱藏在React中的函數式寶石 - 函數式 setState!ide
好吧,這個名字只是我剛剛編造的......並且這並非全新的東西或祕密。不,不徹底是。其實它是React內置的一種模式,只有不多有開發人員知道這種模式。 它歷來沒有名字,但如今它能夠叫作 - 函數式 setState!
正如Dan Abramov所描述的,函數式 setState 就是一種這樣的模式:
「與組件類分開聲明狀態更改。」
咦?
React是一個基於組件的UI庫。組件基本上是一個接受一些屬性並返回UI元素的函數。
function User(props) {
return (
<div>A pretty user</div>
);
}
複製代碼
組件可能須要擁有並管理其狀態。在這種狀況下,您一般將組件編寫爲類。而後你的狀態存在於類的constructor
函數中:
class User {
constructor () {
this.state = {
score : 0
};
}
render () {
return (
<div>This user scored {this.state.score}</div>
);
}
}
複製代碼
爲了管理狀態,React提供了一個名爲setState()
的特殊方法。用法以下:
class User {
...
increaseScore () {
this.setState({score : this.state.score + 1});
}
...
}
複製代碼
請注意setState()
的工做原理。您傳遞一個包含要更新的 state 部分的對象。換句話說,您傳遞的對象將具備與組件 state 中的鍵對應的鍵,而後setState()
經過將對象合併到 state 來更新或設置 state。這就是「set-State」
還記得咱們說的setState()
的工做原理嗎?那麼,若是我告訴你能夠傳遞一個函數來代替傳遞一個對象呢?
是的。setState()
也接受一個函數來做爲參數。該函數接受組件的先前 state 和 當前的 props,它用於計算並返回下一個 state。以下所示:
this.setState(function (state, props) {
return {
score: state.score - 1
}
});
複製代碼
請注意,setState()
是一個函數,咱們將另外一個函數傳遞給它(函數式編程...函數式 setState)。乍一看,代碼可能看起來很醜陋,只有設置狀態的步驟太多了。但爲何還得這樣作呢?
關鍵在於,狀態更新多是異步的。
想一想調用setState()
時會發生什麼。React將首先將傳遞給setState()
的對象合併到當前狀態。而後它將開始合併。它將建立一個新的React Element樹(UI的對象表示),將新樹與舊樹進行區分,根據傳遞給setState()
的對象找出已更改的內容,而後最終更新DOM。
呼!這麼多工做!實際上,這甚至是一個簡化過的總結。可是相信React:
React does not simply 「set-state」.
因爲涉及的工做量很大,調用setState()
可能不會當即更新您的狀態。
React能夠將多個
setState()
的調用批處理成單個更新來提升性能。
上面這句話是什麼意思?
首先,「屢次調用setState()
」可能意味着在一個函數內屢次調用setState()
,以下所示:
state = {score : 0};
// 屢次調用`setState()
increaseScoreBy3 () {
this.setState({score : this.state.score + 1});
this.setState({score : this.state.score + 1});
this.setState({score : this.state.score + 1});
}
複製代碼
如今,當React遇到「屢次調用setState()
」,而不是整整三次執行「set-state」時,React將避免我上面描述的大量工做並巧妙地對本身說:「不! 我不打算愚公移山,每次都更新一些狀態。我寧願獲得一個容器,將全部這些切片包裝在一塊兒,只需更新一次。「這就是批處理!
請記住,傳遞給setState()
的是一個普通對象。如今,假設任什麼時候候React遇到「屢次調用setState()
」,它經過提取傳遞給每一個setState()
調用的全部對象來完成批處理,將它們合併在一塊兒造成一個對象,而後使用該單個對象來執行setState()
。
在JavaScript中,合併對象可能以下所示:
const singleObject = Object.assign(
{},
objectFromSetState1,
objectFromSetState2,
objectFromSetState3
);
複製代碼
這種模式稱爲對象組合。
在JavaScript中,「合併」或組合對象的方式是:若是三個對象具備相同的鍵,則傳遞給Object.assign()的最後一個對象的鍵值將做爲該鍵最終的值。例如:
const me = {name : "Justice"},
you = {name : "Your name"},
we = Object.assign({}, me, you);
we.name === "Your name"; //true
console.log(we); // {name : "Your name"}
複製代碼
由於you
是合併到we
的最後一個對象,因此you
對象中的name
值 - 「Your name」 - 將覆蓋me
對象中name
的值。
所以,若是使用對象做爲參數屢次調用setState()
——每次傳遞一個對象——React將合併。換句話說,它將用咱們傳遞的多個對象中組成一個新對象。 若是任何對象包含相同的鍵,則存儲具備相同鍵的最後一個對象的鍵的值。對吧?
這意味着,鑑於咱們上面的increaseScoreBy3
函數,函數的最終結果將只是1
而不是3
,由於 React 沒有當即按咱們調用setState()
的順序更新狀態。首先,React將全部對象組合在一塊兒,結果以下:{score:this.state.score + 1}
,而後只使用新組合的對象進行「set-state」一次。 像這樣:User.setState({score:this.state.score + 1}
。
要很是清楚,將對象傳遞給setState()
不是問題所在。真正的問題在於當你想要基於前一個狀態計算下一個狀態時,將對象傳遞給setState()
。因此中止這樣作。這不安全!
由於
this.props
和this.state
能夠異步更新,因此不該該依賴它們的值來計算下一個狀態。
索菲亞·舒梅克(Sophia Shoemaker)的這個例子能夠演示這個問題。 演示它,並注意這個例子中的壞和好的解決方案。
若是你沒有花時間演示上面的例子,我強烈建議你仍是先看一下,由於它將幫助你掌握這篇文章的核心概念。
當你演示了上面的例子,你無疑看到函數式setState解決了咱們的問題。但到底是怎麼作的呢?
咱們來諮詢React的核心成員 - Dan。
請注意他給出的答案。
當你使用函數式setState ...
更新將被放進一個隊列,而後按調用順序執行。
所以,當React遇到「屢次函數式setState()
調用」時,React按照「調用它們的順序」對函數進行排隊,而不是將對象合併在一塊兒,(固然,並無要合併的對象)。
以後,React繼續經過調用「隊列」中的每一個函數來更新狀態,將它們傳遞給先前的狀態 - 即,在第一個函數setState()調用以前的狀態(若是當前是第一個函數setState()正在執行)或隊列中前一個函數setState()調用的最新更新的狀態。
下面咱們未來模擬一個setState()方法,這是爲了讓你瞭解React正在作什麼。另外,爲了減小冗長,咱們將使用ES6。若是須要,您隨時能夠編寫ES5版本。
首先,讓咱們建立一個組件類。而後,在其中,咱們將建立一個假的setState()方法。此外,咱們的組件將具備increaseScoreBy3()方法,該方法將執行多功能setState。最後,咱們會像 React 所作的那樣實例化該類。
class User{
state = {score : 0};
//let's fake setState
setState(state, callback) {
this.state = Object.assign({}, this.state, state);
if (callback) callback();
}
// 屢次函數式 setState 的調用
increaseScoreBy3 () {
this.setState( (state) => ({score : state.score + 1}) ),
this.setState( (state) => ({score : state.score + 1}) ),
this.setState( (state) => ({score : state.score + 1}) )
}
}
const Justice = new User();
複製代碼
請注意,setState還接受可選的第二個參數 - 回調函數。若是有傳遞這個參數,React 在更新狀態後調用它。
如今,當用戶觸發increaseScoreBy3()
時,React會將多個函數式 setState 放入隊列。咱們不會在這裏僞造這種邏輯,由於咱們的重點是什麼才真的使函數式setState安全。可是你能夠把「排隊」過程的結果想象成一個函數數組,以下所示:
const updateQueue = [
(state) => ({score : state.score + 1}),
(state) => ({score : state.score + 1}),
(state) => ({score : state.score + 1})
];
複製代碼
最後,讓咱們來模擬更新過程:
// 按順序遞歸調用 state 的更新
function updateState(component, updateQueue) {
if (updateQueue.length === 1) {
return component.setState(updateQueue[0](component.state));
}
return component.setState(
updateQueue[0](component.state),
() =>
updateState( component, updateQueue.slice(1))
);
}
updateState(Justice, updateQueue);
複製代碼
沒錯,這不是一個很棒的代碼,你確定能夠寫出更好的代碼。但這裏的關鍵焦點是每次 React 執行函數 setState 中的函數時,React 都會經過向其傳遞更新 state 的新副原本更新您的狀態。這使得函數 setState 能夠基於前一次的 state 來設置新的 state。 在這裏,我用完整的代碼建立了一個bin。
我把這個例子補充完整,便於大家能夠更好地理解它。
class User{
state = {score : 0};
//fake setState
setState(state, callback) {
console.log("state", state);
this.state = Object.assign({}, this.state, state);
if (callback) callback();
}
}
const Justice = new User();
const updateQueue = [
(state) => ({score : state.score + 1}),
(state) => ({score : state.score + 1}),
(state) => ({score : state.score + 1})
];
// 按順序遞歸調用 state 的更新
function updateState(component, updateQueue) {
if (updateQueue.length === 1) {
return component.setState(updateQueue[0](component.state));
}
return component.setState(
updateQueue[0](component.state),
() =>
updateState( component, updateQueue.slice(1))
);
}
複製代碼
運行一下這段代碼,確保你看懂它。當你回來時咱們會看到是什麼讓函數式的setState真正變得閃閃發光。
到目前爲止,咱們已經深刻探討了爲何在React中執行多個函數式setStates是安全的。可是咱們實際上尚未完成函數式setState的完整定義:「聲明狀態更改與組件類分開」。
多年來,setting-state 的邏輯——即咱們傳遞給setState()的函數或對象 - 老是存在於組件類中,這更像是命令式的而非聲明式的。
那麼今天,我向你展現新出土的寶藏 - 最好的React祕密:
感謝Dan Abramov!
這是函數式setState的強大功能。在組件類以外聲明狀態更新邏輯。而後在組件類中調用它。
// outside your component class
function increaseScore (state, props) {
return {score : state.score + 1}
}
class User{
...
// inside your component class
handleIncreaseScore () {
this.setState( increaseScore)
}
...
}
複製代碼
這是聲明性的!您的組件類再也不關心狀態更新。它只是聲明它想要的更新類型。
要深入理解這一點,請考慮那些一般具備許多狀態切片的複雜組件,在不一樣操做更新每一個切片。有時,每一個更新功能都須要多行代碼。全部這些邏輯都將存在於您的組件中。但之後再也不是這樣了!
另外,我喜歡讓每一個模塊都儘量短。若是你像我同樣以爲你如今的模塊太長了,您能夠將全部狀態更改邏輯提取到其餘模塊,而後導入並在組件中使用它。
import {increaseScore} from "../stateChanges";
class User{
...
// inside your component class
handleIncreaseScore () {
this.setState( increaseScore)
}
...
}
複製代碼
如今,您甚至能夠在另外一個組件中重用increaseScore函數,只需導入它。
你還能夠用函數式setState作什麼?
讓測試變得簡單!
你也能夠傳遞額外的參數來計算下一個狀態(這個讓我大吃一驚...... )
期待更多......
嘿,丹!最後(再說)一句話(來展望下 React)?
若是你已經看到這裏,你可能會像我同樣興奮。當即開始嘗試使用函數式setState!
快樂擼碼!