譯文,原文來自 https://www.robinwieruch.de/l...
譯者前注: 翻譯僅做爲我的學習用途,因本人水平有限,譯文中充斥着很多拙劣文法和表述,最好仍是看英文原文.
狀態管理是很複雜的.視圖層工具庫,如React,容許咱們在組件內部管理狀態.但它只能擴展到具體某一個組件.React僅僅是一個視圖層庫.最終你決定(把狀態管理)遷移到一個更爲成熟的解決方案,如Redux.接下來我想在這篇文章中指出在跳上Redux的列車錢,你應該瞭解清楚的有關React的內容.html
一般人們會同時學習React和Redux,但這有一些缺點:vue
他們不會遇到在僅使用本地組件狀態(this.state)時,擴展狀態管理時出現的問題node
他們不會去學習在React中怎麼進行本地組件的狀態管理react
由於上述緣由,一般建議是先學習React,而後在稍後的時間選擇加入Redux.但若是遇到擴展狀態管理的問題,就選擇加入Redux吧.通常那些擴展問題僅會在較大型的應用程序中存在,一般狀況下你不須要Redux這樣的狀態管理庫.學習React之路一書中演示瞭如何使用普通的React構建應用程序,而不須要用到Redux這樣的外部依賴.git
無論怎麼樣,如今你已經決定擁抱Redux了,所以這裏我列出了在使用Redux以前,你應該瞭解的有關React的內容.github
上面已經提到了最好先學習React,所以你就不能避免使用this.setState()
和this.state
來操做本地狀態來爲你的組件注入生命.你應該要能遊刃有餘地使用它們.編程
class Counter extends React.Component { constructor(props) { super(props); this.state = { counter: 0 }; } render() { return ( <div> Counter: {this.state.counter} <button type="button" onClick={() => this.setState({ counter: this.state.counter + 1 })} /> </div> ); } }
一個React組件能夠在構造函數中定義初始狀態.以後就能夠經過this.setState()
方法來更新狀態.狀態對象(state object)的更新過程是一次淺合併.所以你能夠只更新本地狀態中特定的某一部分狀態,而其他的狀態都不會受到影響.一旦狀態更新完,組件就會從新渲染.在上面的例子中,應用會展現更新後的值:this.state.counter
.基本上在React的單向數據流中只會存在一條閉環.redux
this.setState()
方法是異步更新本地狀態的.所以你不能依賴狀態更新的時機.狀態最終都會更新的.這在大部分狀況下也是沒有什麼問題的.設計模式
儘管如此,想象一下你的組件須要經過當前狀態去計算下一狀態.就如同上面的例子那樣.數組
this.setState({ counter: this.state.counter + 1 });
用於計算的本地狀態(this.state.counter)只是當前時間的一個快照而已.所以當你用this.setState()
更新本地狀態時,而本地狀態又在異步執行更新完成以前改變了,這時你就操做了一箇舊的狀態.第一次遇到相似問題的時候或許會有點難以理解.因此用代碼來代替千言萬語的解釋吧:
this.setState({ counter: this.state.counter + 1 }); // this.state: { counter: 0 } this.setState({ counter: this.state.counter + 1 }); // this.state: { counter: 0 } this.setState({ counter: this.state.counter + 1 }); // this.state: { counter: 0 } // 更新事後的state: { counter: 1 } // 而不是: { counter: 3 }
就如你看到的那樣,當根據本地狀態更新狀態時,本地狀態做爲更新狀態.這會致使bug的.這也是爲何會有第二種更新React本地狀態的方式.
this.setState()
函數能夠接受一個函數做爲參數而非對象.而這個回調函數的調用會傳入在當下this.setState()
異步執行後的本地狀態做爲參數.這個回調執行的時候就能獲取到當前最新的,可信賴的本地狀態.
this.setState(previousState => ({ counter: previousState.counter + 1 }));
那麼當你須要根據以前的本地狀態來更新時,就可使用傳入函數給this.setState()
而非對象.
另外,這也適用於依賴props的更新.在異步執行更新以前,從父組件獲取到的props也有可能被改變過.因此傳入this.setState()
的回調會被注入第二個參數props.
this.setState((prevState, props) => ...);
這樣你就能保證更新狀態時所依賴的state和props是正確的.
this.setState((prevState, props) => ({ counter: prevState.counter + props.addition }));
使用回調函數時的另一個好處是能單獨對狀態更新進行測試.簡單地把this.setState(fn)
中的回調函數提取出來並導出(export)便可.這個回調函數應該是一個純函數,你能夠根據輸入進行簡單的輸出測試.
State是組件內部維護狀態.能夠做爲其餘組件的Props向下傳遞.那些接受Props的組件能夠在內部使用Props,或者再進一步向下傳遞給它們的子組件.另外子組件接受的Props仲一樣能夠是來自父組件的回調函數.那些函數能夠用於改變父組件State.基本上Props隨着組件樹往下傳遞,而State則由組件本身維護,此外經過往上層組件冒泡的函數能夠改變組件中的State,而更新事後的State又以Props的形式往下傳遞.
組件能夠管理不少State,這些State能夠做爲Props往下傳遞給子組件而且Props中能夠傳遞函數給予子組件修改父組件的State.
可是,子組件並不知道Props中的函數的來源或功能.這些函數能夠更新父組件的State,也多是執行其餘操做.一樣的道理,子組件也不知道它所接收的Props是來自父組件的Props,State或其餘派生屬性,子組件只是單純的使用它們而已.
掌握並理解State和Props很是重要,組件樹中使用的全部屬性均可以被分爲State和Props(以及根據State和Props計算得出的派生屬性).全部須要交互的部分都應做爲State保存,而其餘的一切均可以做爲Props在組件樹中傳遞.
在使用複雜的狀態管理工具庫以前,你應該已經試過在組件樹中往下傳遞Props.當你傳遞Props給一些根本不使用它們的組件,而又須要這些組件把Props繼續向下傳遞給最後一個使用它們的子組件時,你應該已經感受到"須要一種更好的方式來作這件事".
你是否已經提取出你的本地狀態層?這是在React中擴展本地狀態管理最重要的策略.狀態層是能夠上下提取的.
你能夠向下提取的你的本地狀態,使其餘組件無法訪問.假設你有一個組件A是組件B和組件C的父組件,B和C是A的子組件,而且B,C爲兄弟組件.組件A是惟一維護本地狀態(State)的組件,可是它會將State做爲Props傳遞給子組件.另外也傳遞了必要的函數讓B和C可以改變A中的State.
+----------------+ | | | A | | | | Stateful | | | +--------+-------+ | +---------+-----------+ | | | | +--------+-------+ +--------+-------+ | | | | | | | | | B | | C | | | | | | | | | +----------------+ +----------------+
如今組件A的State中有一半做爲Props傳遞給C併爲C所用,但B並不須要那些Props.另外,C使用其接收的Props中的函數來改變A中僅傳遞給了C的那部分State.如圖所示,組件A在幫助組件C維護着State.在大多數狀況下,只須要一個組件管理其子組件全部的State便可.可是想象一下,若是組件A和組件C中間還有其餘組件,而組件A依然是在維護着組件C所需的狀態,那由組件A往下傳遞的全部Props都須要遍歷組件樹才能最終到達組件C.
+----------------+ | | | A | | | | | | Stateful | +--------+-------+ | +---------+-----------+ | | | | +--------+-------+ +--------+-------+ | | | | | | | + | | B | | |Props | | | | v | | | | | +----------------+ +--------+-------+ | +--------+-------+ | | | + | | |Props | | v | | | +--------+-------+ | +--------+-------+ | | | | | C | | | | | +----------------+
這是展現提取React State的完美用例.當State僅僅用於組件C而組件A只是充當了維護的角色.這個時候對應的State片斷就能夠在在C中單獨管理,是能夠被獨立出來的.將State狀態管理提取出來到組件C後,就不會出現傳遞Props須要遍歷整個組件樹的狀況了.
+----------------+ | | | A | | | | | | Stateful | +--------+-------+ | +---------+-----------+ | | | | +--------+-------+ +--------+-------+ | | | | | | | | | B | | | | | | | | | | | +----------------+ +--------+-------+ | +--------+-------+ | | | | | | | | | | +--------+-------+ | +--------+-------+ | | | | | C | | | | Stateful | +----------------+
此外,組件A中的State也應有所改變,它只管理本身以及其最接近的子組件的必要State.
提取State不只能夠往下,也能夠反過來往上提取:提高狀態.假設你有父組件A,組件B和C都爲其子組件.A和B以及A和C之間又多少個組件並不重要,可是此次組件C已經管理了它所需的全部State.
+----------------+ | | | A | | | | | | Stateful | +--------+-------+ | +---------+-----------+ | | | | +--------+-------+ +--------+-------+ | | | | | | | | | B | | | | | | | | | | | +----------------+ +--------+-------+ | +--------+-------+ | | | | | C | | | | Stateful | +----------------+
若是組件B須要組件C中所管理的State呢?這個時候組件C中的State不能共享給組件B,由於State只能做爲Props向下傳遞.這就是爲何你須要提高State.你能夠把組件C中的State網上提取,直到B和C的共同父組件(A),若是組件B須要用到組件C中管理的全部狀態,則組件C甚至應該變成無狀態組件.而全部的State能夠在A中管理,但在B和C之間共享.
+----------------+ | | | A | | | | | | Stateful | +--------+-------+ | +---------+-----------+ | | | | +--------+-------+ +--------+-------+ | | | | | | | + | | B | | |Props | | | | v | | | | | +----------------+ +--------+-------+ | +--------+-------+ | | | | | C | | | | | +----------------+
提取State讓你可以僅僅經過React就能擴展你的狀態管理.當更多的組件須要用到特定的State時,能夠往上提取State,直到須要訪問該State的組件的公共組件.此外,本地狀態管理依然保持着可維護性,由於一個組件根據自身需求管理儘量多的狀態,換言之若是組件或其子組件不須要該State的話,則能夠往下提取State放置在須要的地方.
你能夠在官方文檔中閱讀更多關於關於提取State的信息.
高階組件是React中一種高級設計模式.你可使用它來抽象功能,並將其做爲其餘多個組件的可選功能重用.高階組件接受一個組件和其餘可選配置做爲參數並返回一個加強版本的組件.它創建在Javascript的高階函數(返回函數的函數)的原則之上.
若是你並不十分了解高階組件的概念,我推薦你閱讀一下高階組件的簡單介紹.裏面經過React的條件渲染用例來說解高階組件的概念.
高階組件概念在後面會顯得尤其重要,由於在使用像Redux這樣的庫的時候,你將會遇到不少高階組件.當須要使用Redux這一類庫將狀態管理層和React的視圖層"鏈接"起來時.你一般會使用一個高階組件來處理這層關係(如react-redux中的connect高階組件).
這也一樣適用於其餘狀態管理庫,如MobX.在這些庫中使用了高階組件來處理狀態管理層和視圖層的鏈接.
React的Context上下文不多被使用,我不會建議去使用它,由於Context API並不穩定,並且使用它還UI增長應用程序的複雜性.不過儘管如此,仍是頗有必要理解它的功能的.
因此爲何你應該要了解Context呢?Content用於在組件樹上隱式地傳遞屬性.你能夠在父組件的某個地方聲明屬性,並在組件樹下的某個子組件中選擇再次獲取該屬性.然而若是經過Props傳遞的話,全部不須要使用那些數據的組件都須要顯式地往下傳遞.這些組件位於父組件的上下文和最終消費該Props的子組件的上下文之間.因此Context是一個無形的容器.你能夠在組件樹中找到它.因此,爲何你應該要了解Context呢?
一般在使用複雜的狀態管理工具庫時,例如Redux和MobX,你須要將狀態管理層粘合到React的視圖層上.這也是爲何你須要瞭解React高階組件的緣由.這其中的粘合層容許你訪問State並進行修改,而State自己一般是在某種狀態容器中進行管理的.
可是如何使這個狀態容器可以被全部粘合上React組件所訪問呢?這是由React Context來完成的.在最頂層的組件,通常是React應用的根組件,你應在React Context中聲明狀態容器,以便在組件樹下的每一個組件都能進行隱式訪問.整個過程都是經過React的提供者模式(Provider Pattern)完成的.
固然這也並不意味着在使用Redux一類的庫時你須要本身處理React Context上下文.這類工具庫已經爲你提供瞭解決方案,使全部組件均可以訪問狀態容器.當你的狀態能夠在不一樣的組件中訪問而沒必要擔憂狀態容器來自哪裏的時後,這個底層實現的機制是什麼,爲何這樣作的有效的,這都是頗有必要去了解的事實.
React中有兩種聲明組件的方式: ES6類組件和函數(不帶狀態)組件.一個不帶狀態的函數組件僅僅是一個接收Props並返回JSX的函數.其中不保持任何的State也不會觸發任何React生命週期函數.顧名思義就是無狀態的.
function Counter({ counter }) { return ( <div> {counter} </div> ); }
另外一方面,即React類組件是能夠保持State和能出發聲明周期函數的.這些組件能訪問this.state
和調用this.setState()
方法.這就說明了ES類組件是能帶狀態的組件.而若是他們不須要保持本地State的話,也能夠是無狀態組件.一般無狀態的類組件也會須要使用聲明周期函數.
class FocusedInputField extends React.Component { constructor(props) { super(props); } componentDidMount() { this.input.focus(); } render() { return ( <input type="text" value={this.props.value} ref={node => this.input = node} onChange={event => this.props.onChange(event.target.value)} /> ); } }
結論是隻有ES6類組件是能夠帶狀態的,可是他們也能夠是無狀態的.而函數組件則是無狀態的.
此外,還可使用高階組件來添加狀態到React組件上.你能夠編寫本身的高階組件來管理狀態,或者使用像recompose工具庫中的withState
高階組件.
import { withState } from `recompose`; const enhance = withState('counter', 'setCounter', 0); const Counter = enhance(({ counter, setCounter }) => <div> Count: {counter} <button onClick={() => setCounter(n => n + 1)}>Increment</button> <button onClick={() => setCounter(n => n - 1)}>Decrement</button> </div> );
當使用高階組件時,你能夠選擇傳遞任意局部狀態到React組件中去.
由於Dan Abramov的博文,容器和演示模式變得流行了起來.若是你對此並不十分了解,如今正是深刻學習的時機.基本上它會將組件分爲兩類:容器組件和展現組件.容器組件負責描述組件是如何工做的,展現組件負責組件內容的展現.容器組件通常是一個類組件,由於容器組件是須要管理本地狀態的.而展現組件是一個無狀態函數組件,由於通常只用於展現Props和調用從父組件傳遞過來的函數.
在深刻Redux以前,理解這種模式背後的原理是頗有意義的.當你使用狀態管理的工具庫時,你會把組件和State鏈接起來.那些組件並不在乎應該怎麼去展現內容,而更可能是描述如何起效的.所以那些組件就是容器組件.再具體一點,你應該會常常聽到鏈接組件(connected component)
當一個組件和狀態管理層鏈接起來以後.
縱觀全部狀態管理庫,Redux是最流行的一個,可是MobX也是一個頗有價值的替代品.這兩個庫都遵循不一樣的哲學和編程範式.
在你決定使用它們以前,請確保你清楚瞭解本文中解釋的有關React的內容.你應該對能坦然面對在僅使用React的狀況下進行本地狀態管理,還能瞭解各類React的概念並擴展你的狀態管理.此外,確保在你的應用在將來會變得更加龐大時,才須要去擴展狀態管理的解決方案.也許提取State或使用React Context應用提供者模式(provider pattern)就能夠解決你的問題了.
所以,若是決定邁上Redux和MobX的道路,能夠閱讀下面的文章以作出更好的決定:Redux or MobX: An attempt to dissolve the Confusion.文章中有效的對比了兩個庫的差別,並提供了一些學習和應用他們的建議.或者看下Tips to learn React + Redux來了解Redux吧.
但願這篇文章爲你理清了再應用像Redux一類的庫以前,你應該學習和了解的內容.目前,我正在寫一個關於Redux和本地狀態管理的書,內容包括Redux和MobX.若是不想錯過的話,你能夠點這進行訂閱.
譯者後注: 但願我拙劣的翻譯沒有爲你理解本文增長難度,再說一次最好仍是看英文原文,若有改進建議請大方聯繫,我必虛心受教.