[2018-07-26 Update, RN版本已更新至0.56版本]
[2017-02-17 Update尘应,github上代碼的RN版本已更新至最新版本(0.41.2)怎茫,如有問(wèn)題請(qǐng)?zhí)醝ssue泞莉,3Q]
redux是什么?
redux
是一個(gè)用于管理js應(yīng)用狀態(tài)的容器。redux
出現(xiàn)時(shí)間并不是很長(zhǎng),在它出現(xiàn)之前也有類似功能的模塊出現(xiàn),諸如flux
等等降狠。redux
設(shè)計(jì)的理念很簡(jiǎn)單,似乎最初這個(gè)開發(fā)團(tuán)隊(duì)就有讓redux
可以方便融入在server, browser, mobile client端的打算庇楞。目前在github上redux-*
的第三方中間件榜配、插件越來(lái)越多。如果react項(xiàng)目中想使用redux
姐刁,那么就有react-redux
插件來(lái)完成配合芥牌。而作為開發(fā)者,可以使用這些優(yōu)秀的第三方資源來(lái)開發(fā)/優(yōu)化已有的項(xiàng)目聂使,也是一件很歡樂(lè)的事壁拉。
設(shè)計(jì)的動(dòng)機(jī)
在redux.js.org里有關(guān)于編寫redux的動(dòng)機(jī)描述(our code must manage more state than ever before
),在現(xiàn)實(shí)的生活中柏靶,單頁(yè)應(yīng)用越來(lái)越多弃理,而且需要維護(hù)的狀態(tài)也越來(lái)越復(fù)雜,諸如維護(hù)數(shù)據(jù)更新屎蜓、UI更新痘昌、本地?cái)?shù)據(jù)存儲(chǔ)等這些都是我們?cè)趈s應(yīng)用常常需要處理的情景,當(dāng)然這里多數(shù)都會(huì)涉及到異步處理炬转。而redux
本身就是為了解決這些問(wèn)題辆苔,而是將所有的變化進(jìn)行統(tǒng)一流程處理,會(huì)使我們的程序狀態(tài)變化清晰可見扼劈。redux最終目的就是讓狀態(tài)(state)變化變得可預(yù)測(cè)驻啤。
使用的三原則
-
1, Single source of truth
單一數(shù)據(jù)源。整個(gè)應(yīng)用的state荐吵,存儲(chǔ)在唯一一個(gè)object中骑冗,同時(shí)也只有一個(gè)store用于存儲(chǔ)這個(gè)object. -
2, State is read-only
狀態(tài)是只讀的赊瞬。唯一能改變state的方法,就是觸發(fā)action操作贼涩。action是用來(lái)描述正在發(fā)生的事件的一個(gè)對(duì)象巧涧。 - ** 3, Changes are made with pure functions**
在改變state tree時(shí),用到action遥倦,同時(shí)也需要編寫對(duì)應(yīng)的reducers才能完成state改變操作谤绳。
在上面的三原則中,我們看到了store
, action
, reducer
這些詞谊迄,那就先說(shuō)說(shuō)redux
是怎么進(jìn)行應(yīng)用狀態(tài)(state)維護(hù)管理的呢闷供。
redux狀態(tài)管理的流程
- action是用戶觸發(fā)或程序觸發(fā)的一個(gè)普通對(duì)象。
- reducer是根據(jù)action操作來(lái)做出不同的數(shù)據(jù)響應(yīng)统诺,返回一個(gè)新的state歪脏。
-
store的最終值就是由reducer的值來(lái)確定的。(一個(gè)store是一個(gè)對(duì)象, reducer會(huì)改變store中的某些值)
上圖簡(jiǎn)單畫了下redux狀態(tài)改變的流程粮呢。action
-> reducer
-> 新store
-> 反饋到UI上有所改變
婿失。
下面再給個(gè)具體實(shí)例:
store用于維護(hù)狀態(tài)的容器,包括了應(yīng)用的多個(gè)狀態(tài)啄寡,比如說(shuō)用戶是否登錄豪硅、用戶信息、用戶任務(wù)等等挺物。action是一個(gè)普通對(duì)象懒浮,用于指明是哪種操作,這樣才能在reducers中進(jìn)行識(shí)別识藤。而眾多reducer是負(fù)責(zé)返回新的state的函數(shù)砚著。在實(shí)際應(yīng)用中,你需要將store或store的某個(gè)值綁定到界面痴昧,這樣更新store的時(shí)候稽穆,該頁(yè)面可以監(jiān)聽到值的更新,然后進(jìn)行一些頁(yè)面更新操作/跳轉(zhuǎn)操作等赶撰。
redux在實(shí)際使用中需要用到的高級(jí)部分
redux設(shè)計(jì)如此簡(jiǎn)潔舌镶,以至于并沒有進(jìn)行異步處理的功能餐胀。但是留下了middleware這個(gè)概念惕虑。可以自己編寫符合需要的中間件。目前第三方的中間件基本可以完成一個(gè)復(fù)雜應(yīng)用的架構(gòu)設(shè)計(jì)。那就先說(shuō)一說(shuō)暗膜,怎么去處理異步請(qǐng)求呢瑞佩。
首先推薦redux-thunk
蜒蕾,可以看到它的源碼很簡(jiǎn)潔瑟匆。就是判斷action是否是函數(shù),如果是函數(shù)進(jìn)行遞歸式的操作渐扮。所以在redux
中的異步膀估,只能出現(xiàn)在action中饼记,而且還需要有中間件的支持。
export default function thunkMiddleware({ dispatch, getState }) {
return next => action => {
if (typeof action === 'function') {
return action(dispatch, getState);
}
return next(action);
};
}
// redux-thunk的源碼
同步action與異步action最大的區(qū)別是:
同步只返回一個(gè)普通action對(duì)象束昵。而異步操作中途會(huì)返回一個(gè)promise函數(shù)巴比。當(dāng)然在promise函數(shù)處理完畢后也會(huì)返回一個(gè)普通action對(duì)象。thunk
中間件就是判斷如果返回的是函數(shù)礁遵,則不傳導(dǎo)給reducer轻绞,直到檢測(cè)到是普通action對(duì)象,才交由reducer處理佣耐。
實(shí)例說(shuō)說(shuō)redux的使用政勃。
實(shí)例代碼在github上查看地址。該實(shí)例只演示了登錄過(guò)程兼砖,是比較基礎(chǔ)的redux使用案例奸远。
項(xiàng)目相關(guān)代碼均在js
目錄下。目錄中可以看到actions
, reducers
, store
等子目錄讽挟。
實(shí)例action
actions/user.js
目錄中定義了用戶登錄操作的action creator
:
- 第22行的
logIn
logIn
是一個(gè)異步action懒叛,注意函數(shù)內(nèi)部的寫法與redux-thunk
的定義要相同。 - 第38行的
skipLogin
- 第47行
logOut
這些creator耽梅,產(chǎn)生的action狀態(tài)有LOGGED_DOING
,LOGGED_IN
,LOGGED_ERROR
,LOGGED_OUT
四種狀態(tài)薛窥。
第8行變量testUser
, 第15行變量skipUser
分別是模擬針對(duì)普通登錄成功后(這里用的是fetch www.baidu.com,真實(shí)情況下需換成真實(shí)的登錄接口)的用戶對(duì)象眼姐,跳過(guò)登錄后默認(rèn)的用戶對(duì)象诅迷。
接下來(lái)再看看reducers
怎么去處理這些action?
實(shí)例reducer
reducers/user.js
中:
- 第5行的
initialState
定義了最開始的應(yīng)用狀態(tài)(即用戶未登錄的情況下的state)佩番。 - 第13行,對(duì)每個(gè)傳過(guò)來(lái)的
action
進(jìn)行switch
竟贯,每個(gè)action都需要返回一個(gè)state對(duì)象答捕,如果不需要變動(dòng),則返回原對(duì)象(switch中的default返回值)屑那。需要變動(dòng),則返回一個(gè)新的state, 可以看到當(dāng)type為LOGGED_DOING
,LOGGED_IN
,LOGGED_OUT
時(shí)艘款,返回的對(duì)象跟原始對(duì)象都會(huì)有一些字段的差別持际。
reducers/index.js
中:
- 第5行
combineReducers
是將應(yīng)用的state進(jìn)行組合。 - 目前demo中只有用戶信息哗咆,所以只看到第6行
userStore
這一個(gè)key蜘欲,在一個(gè)業(yè)務(wù)復(fù)雜的應(yīng)用里,需要保存很多應(yīng)用和用戶交互產(chǎn)生的信息(比如說(shuō)用戶聊天列表等信息)晌柬。
實(shí)例store
再來(lái)看看store
的處理:
store/index.js
中定義了store的行為(包括中間件):
- 第23行的
applyMiddleware
會(huì)將中間件應(yīng)用在redux action過(guò)程中姥份。 - 第10行自定義一個(gè)
logger
中間件,該中間件的目的是打印出當(dāng)前的觸發(fā)的action
以及出發(fā)后的state
變化年碘。 - 第27行澈歉,33行 使用了
redux-persist
這個(gè)第三方插件來(lái)將store對(duì)象存儲(chǔ)到本地,以及從本地恢復(fù)數(shù)據(jù)到store中屿衅,比如說(shuō)保存登錄信息埃难,下次打開應(yīng)用可以直接跳過(guò)登錄界面。
程序入口
上面是定義涤久。下面再來(lái)看如何在程序中使用:
在入口文件
index.js
中:
- 第27行涡尘,需要將
store
作為屬性傳遞給Provider
組件中。 - 第28行响迂,可以看到的是真正渲染出的東西是
<Root/>
標(biāo)簽返回的東西考抄。
那就來(lái)看看root.js
里的內(nèi)容。
root.js
中:
- 第48行的
render
主要實(shí)現(xiàn)了Navigator
導(dǎo)航器的處理蔗彤,并用自定義的一個(gè)Router
(第27行)進(jìn)行封裝了下川梅。 - 第59行,在末尾的
select
函數(shù)幕与,是將store
中的某些值復(fù)制到當(dāng)前組件的props中挑势,注意這里需要用connect
函數(shù)進(jìn)行綁定,否則store
變化啦鸣,不會(huì)反饋到Root
組件中潮饱。isLoggedIn
這個(gè)變量便被復(fù)制到當(dāng)前的Root組件中,在Root內(nèi)部方法中可以訪問(wèn)诫给。 - 第16行香拉,
constructor
里啦扬,會(huì)對(duì)登錄狀態(tài)進(jìn)行判斷,如果檢測(cè)到已經(jīng)登錄了凫碌,則會(huì)修改Navigator
初始的路由設(shè)置(第10行設(shè)置的)扑毡,使應(yīng)用直接顯示MainPage
。
登錄頁(yè)面盛险。
那再看看登錄頁(yè)面pages/login.js
中的用戶操作:
上面的3個(gè)圖片是整個(gè)pages/login.js
的源碼瞄摊。
- 第160行,指定了該組件會(huì)與
store
的哪些值進(jìn)行連接苦掘。分別是isLoggedIn
,user
,status
换帜。 - 第129行,定義了
Text
的onPress
操作鹤啡。handleLogin
是綁定的操作方法惯驼,方法內(nèi)容見第60行。在71行時(shí)递瑰,會(huì)觸發(fā)logIn
這個(gè)action祟牲。 - 觸發(fā)
logIn
后,會(huì)先進(jìn)入LOGGED_DOING
狀態(tài)抖部,此時(shí)說(shuō)明在登錄中说贝。在第42行,有對(duì)該狀態(tài)進(jìn)行監(jiān)聽您朽,如果為該狀態(tài)狂丝,會(huì)將彈窗彈出,提醒為loading態(tài)哗总。 - 第33行几颜,在
shouldComponentUpdate
中,對(duì)即將變化的nextProps進(jìn)行與目前的props進(jìn)行對(duì)比讯屈。比如說(shuō)logIn
執(zhí)行登錄完成后蛋哭,第35行檢測(cè)到isLoggedIn
為true,則執(zhí)行toMain
函數(shù)(即跳轉(zhuǎn)到主頁(yè)中)涮母。
看下最終的demo交互效果谆趾。
推薦:
RNTools是一個(gè)分享React Native文章、實(shí)例代碼以及第三方模塊的平臺(tái)叛本。RNTools官網(wǎng)鏈接 RNTools應(yīng)用下載