目錄
- 什么是Redux
- Redux 優(yōu)點(diǎn)
- Redux 的三個(gè)基本原則
- Redux 有那幾部分組成
- Action
- reducer
- Store
- 異步Action
- 配置React Native與Redux開(kāi)發(fā)
- 第一步
- 第二步
- 第三步
什么是Redux
Redux 是 JavaScript 狀態(tài)容器骗炉,提供可預(yù)測(cè)化的狀態(tài)管理,可以讓你構(gòu)建一致化的應(yīng)用振惰,運(yùn)行于不同的環(huán)境(客戶(hù)端睹耐、服務(wù)器嵌削、原生應(yīng)用),并且易于測(cè)試。數(shù)據(jù)是單向流動(dòng)的。
工作流程
- 用戶(hù)(操作View)發(fā)出Action聪铺,發(fā)出方式就用到了dispatch方法;
- 然后萄窜,Store自動(dòng)調(diào)用Reducer铃剔,并且傳入兩個(gè)參數(shù)(當(dāng)前State和收到的Action)撒桨,Reducer會(huì)返回新的State,如果有Middleware键兜,Store會(huì)將當(dāng)前State和收到的Action傳遞給Middleware凤类,Middleware會(huì)調(diào)用Reducer 然后返回新的State;
- State一旦有變化普气,Store就會(huì)調(diào)用監(jiān)聽(tīng)函數(shù)谜疤,來(lái)更新View;
Middleware:可以讓你在reducer
執(zhí)行前與執(zhí)行后進(jìn)行攔截并插入代碼现诀,來(lái)達(dá)到操作action和Store的目的夷磕,這樣一來(lái)就很容易實(shí)現(xiàn)靈活的日志打印、錯(cuò)誤收集仔沿、API請(qǐng)求坐桩、路由等操作。
Redux優(yōu)點(diǎn)
- 可預(yù)測(cè): 始終有一個(gè)唯一的準(zhǔn)確的數(shù)據(jù)源(single source of truth)就是store封锉,通過(guò)actions和reducers來(lái)保證整個(gè)應(yīng)用狀態(tài)同步绵跷,做到絕不混亂
- 易維護(hù): 具備可預(yù)測(cè)的結(jié)果和嚴(yán)格的組織結(jié)構(gòu)讓代碼更容易維護(hù)
- 易測(cè)試: 編寫(xiě)可測(cè)試代碼的首要準(zhǔn)則是編寫(xiě)可以?xún)H做一件事并且獨(dú)立的小函數(shù)(single responsibility principle),Redux的代碼幾乎全部都是這樣的函數(shù):短小·純粹·分離
Redux 的三個(gè)基本原則
- 單一數(shù)據(jù)源:整個(gè)應(yīng)用的 state 被儲(chǔ)存在一棵 object tree 中成福,并且這個(gè) object tree 只存在于唯一一個(gè) store 中碾局;
- State 是只讀的:唯一改變 state 的方法就是觸發(fā) action,action 是一個(gè)用于描述已發(fā)生事件的普通對(duì)象奴艾;
- 使用純函數(shù)來(lái)執(zhí)行修改:為了描述 action 如何改變 state tree 净当,你需要編寫(xiě) reducers;
Redux 有那幾部分組成
action:action就是一個(gè)描述發(fā)生什么的對(duì)象蕴潦;
reducer:形式為 (state, action) => state 的純函數(shù)蚯瞧,功能是根據(jù)action 修改state 將其轉(zhuǎn)變成下一個(gè) state;
store:用于存儲(chǔ)state品擎,你可以把它看成一個(gè)容器埋合,整個(gè)應(yīng)用只能有一個(gè)store。
Action
Action
是把數(shù)據(jù)從應(yīng)用傳到 store
的有效載荷萄传。它是 store
數(shù)據(jù)的唯一來(lái)源畦浓,也就是說(shuō)要改變store中的state就需要觸發(fā)一個(gè)action龄句。
Action 本質(zhì)上一個(gè)普通的JavaScript
對(duì)象誊酌。action
內(nèi)必須使用一個(gè)字符串類(lèi)型的type
字段來(lái)表示將要執(zhí)行的動(dòng)作戚嗅,除了 type 字段外,action 對(duì)象的結(jié)構(gòu)完全由你自己決定衍菱。多數(shù)情況下赶么,type 會(huì)被定義成字符串常量。當(dāng)應(yīng)用規(guī)模越來(lái)越大時(shí)脊串,建議使用單獨(dú)的模塊或文件來(lái)存放 action辫呻。
import { ADD_TODO, REMOVE_TODO } from '../actionTypes'
//action
{
type: ADD_TODO,
text: 'Build my first Redux app'
}
提示:使用單獨(dú)的模塊或文件來(lái)定義 action type 常量并不是必須的清钥,甚至根本不需要定義。對(duì)于小應(yīng)用來(lái)說(shuō)放闺,使用字符串做 action type 更方便些祟昭。不過(guò),在大型應(yīng)用中把它們顯式地定義成常量還是利大于弊的怖侦。
reducer
reducer是根據(jù)action 修改state 將其轉(zhuǎn)變成下一個(gè) state篡悟,記住 actions 只是描述了有事情發(fā)生了這一事實(shí),并沒(méi)有描述應(yīng)用如何更新 state匾寝。
(previousState, action) => newState
保持 reducer 純凈非常重要搬葬。永遠(yuǎn)不要在 reducer 里做這些操作
- 修改傳入?yún)?shù);
- 執(zhí)行有副作用的操作艳悔,如 API 請(qǐng)求和路由跳轉(zhuǎn)急凰;
- 調(diào)用非純函數(shù),如 Date.now() 或 Math.random()很钓。
提示:reducer 是純函數(shù)。它僅僅用于計(jì)算下一個(gè) state董栽。它應(yīng)該是完全可預(yù)測(cè)的:多次傳入相同的輸入必須產(chǎn)生相同的輸出码倦。它不應(yīng)做有副作用的操作,如 API 調(diào)用或路由跳轉(zhuǎn)锭碳。這些應(yīng)該在 dispatch action 前發(fā)生袁稽。
拆分reducer:根據(jù)不同模塊的劃分去查分reducer,減少冗長(zhǎng)擒抛,讓代碼簡(jiǎn)潔推汽。
合并reducer:經(jīng)過(guò)上述的步驟我們將一個(gè)大的reducer拆分成了不同的小的reducer,但redux原則是只允許一個(gè)根reducer歧沪,接下來(lái)我們需要將這幾個(gè)小的reducer聚合到一個(gè)跟reducer中歹撒。
這里我們需要用到Redux 提供的combineReducers(reducers)
。
combineReducers()
所做的只是生成一個(gè)函數(shù)诊胞,這個(gè)函數(shù)來(lái)調(diào)用你的一系列 reducer
暖夭,每個(gè) reducer
根據(jù)它們的 key
來(lái)篩選出 state
中的一部分?jǐn)?shù)據(jù)并處理,然后這個(gè)生成的函數(shù)再將所有 reducer 的結(jié)果合并成一個(gè)大的對(duì)象撵孤。沒(méi)有任何魔法迈着。正如其他 reducers,如果 combineReducers()
中包含的所有 reducers 都沒(méi)有更改 state邪码,那么也就不會(huì)創(chuàng)建一個(gè)新的對(duì)象
Store
是存儲(chǔ)state的容器裕菠,Store 會(huì)把兩個(gè)參數(shù)(當(dāng)前的 state 樹(shù)和 action)傳入 reducer。
store 有以下職責(zé):
- 維持應(yīng)用的 state闭专;
- 提供 getState() 方法獲取 state奴潘;
- 提供 dispatch(action)方法更新 state:我們可以在任何地方調(diào)用 store.dispatch(action)旧烧,包括組件中、XMLHttpRequest 回調(diào)中萤彩、甚至定時(shí)器中粪滤;
- 通過(guò) subscribe(listener) 注冊(cè)監(jiān)聽(tīng)器;
- 通過(guò) subscribe(listener) 返回的函數(shù)注銷(xiāo)監(jiān)聽(tīng)器。
我們使用 combineReducers()
將多個(gè) reducer 合并成為一個(gè)∪阜觯現(xiàn)在我們通過(guò)Redux的 createStore()
來(lái)創(chuàng)建一個(gè)Store杖小。
import { createStore } from 'redux'
import todoApp from './reducers'
let store = createStore(todoApp)
異步Action
上文中所講的Action都是基于同步實(shí)現(xiàn)的,那么對(duì)于網(wǎng)絡(luò)請(qǐng)求數(shù)據(jù)庫(kù)加載等應(yīng)用場(chǎng)景同步Action顯然是不適用的愚墓,對(duì)此我們需要用到異步Action予权。
我們可將異步Action簡(jiǎn)答理解為:在Action中進(jìn)行異步操作等操作返回后再dispatch一個(gè)action。
為了使用異步
action
我們需要引入redux-thunk
庫(kù)浪册,redux-thunk是為Redux提供異步action支持的中間件扫腺。
使用redux-thunk
npm install --save redux-thunk
import thunk from 'redux-thunk'
let middlewares = [
thunk
]
//添加異步中間件redux-thunk
let createAppStore = applyMiddleware(...middlewares)(createStore)
創(chuàng)建異步Action
export function onSearch(inputKey, token, popularKeys) {
return dispatch => {
dispatch({type: Types.SEARCH_REFRESH});
fetch(genFetchUrl(inputKey)).then(response => {//如果任務(wù)取消,則不做任何處理
return checkCancel(token) ? response.json() : null;
}).then(responseData => {
if (!checkCancel(token, true)) {//如果任務(wù)取消村象,則不做任何處理
return
}
if (!responseData || !responseData.items || responseData.items.length === 0) {
dispatch({type: Types.SEARCH_FAIL, message: inputKey + '什么都沒(méi)找到'});
return
}
let items = responseData.items;
getFavoriteKeys(inputKey, dispatch, items, token, popularKeys);
}).catch(e => {
console.log(e);
dispatch({type: Types.SEARCH_FAIL, error: e});
})
}
}
異步數(shù)據(jù)流
默認(rèn)情況下笆环,createStore() 所創(chuàng)建的 Redux store 沒(méi)有使用 middleware,所以只支持 同步數(shù)據(jù)流厚者。
你可以使用 applyMiddleware() 來(lái)增強(qiáng) createStore()躁劣。它可以幫助你用簡(jiǎn)便的方式來(lái)描述異步的 action。
像 redux-thunk 或 redux-promise 這樣支持異步的 middleware 都包裝了 store 的 dispatch() 方法库菲,以此來(lái)讓你 dispatch 一些除了 action 以外的其他內(nèi)容账忘,例如:函數(shù)或者 Promise。你所使用的任何 middleware 都可以以自己的方式解析你 dispatch 的任何內(nèi)容熙宇,并繼續(xù)傳遞 actions 給下一個(gè) middleware鳖擒。比如,支持 Promise 的 middleware 能夠攔截 Promise烫止,然后為每個(gè) Promise 異步地 dispatch 一對(duì) begin/end actions蒋荚。
當(dāng) middleware 鏈中的最后一個(gè) middleware 開(kāi)始 dispatch action 時(shí),這個(gè) action 必須是一個(gè)普通對(duì)象馆蠕;
配置React Native與Redux開(kāi)發(fā)
第一步:在使用 React Navigation 的項(xiàng)目中圆裕,想要集成 redux 就必須要引入 react-navigation-redux-helpers
這個(gè)庫(kù)。
第二步:配置Navigator
/**
* 1.初始化react-navigation與redux的中間件荆几,
* 該方法的一個(gè)很大的作用就是為reduxifyNavigator的key設(shè)置actionSubscribers(行為訂閱者)
* 設(shè)置訂閱者@https://github.com/react-navigation/react-navigation-redux-helpers/blob/master/src/middleware.js#L29
* 檢測(cè)訂閱者是否存在@https://github.com/react-navigation/react-navigation-redux-helpers/blob/master/src/middleware.js#L97
* @type {Middleware}
*/
export const middleware = createReactNavigationReduxMiddleware(
'root',
state => state.nav
);
/**
* 2.將根導(dǎo)航器組件傳遞給 reduxifyNavigator 函數(shù),
* 并返回一個(gè)將navigation state 和 dispatch 函數(shù)作為 props的新組件吓妆;
* 注意:要在createReactNavigationReduxMiddleware之后執(zhí)行
*/
const AppWithNavigationState = reduxifyNavigator(RootNavigator, 'root');
/**
* State到Props的映射關(guān)系
* @param state
*/
const mapStateToProps = state => ({
state: state.nav,//v2
});
/**
* 3.連接 React 組件與 Redux store
*/
export default connect(mapStateToProps)(AppWithNavigationState);
第三步配置Reducer
//1.指定默認(rèn)state
const navState = RootNavigator.router.getStateForAction(RootNavigator.router.getActionForPathAndParams(rootCom));
/**
* 2.創(chuàng)建自己的 navigation reducer,
*/
const navReducer = (state = navState, action) => {
const nextState = RootNavigator.router.getStateForAction(action, state);
// 如果`nextState`為null或未定義吨铸,只需返回原始`state`
return nextState || state;
};
/**
* 3.合并reducer
* @type {Reducer<any> | Reducer<any, AnyAction>}
*/
const index = combineReducers({
nav: navReducer,
theme: theme,
popular: popular,
trending: trending,
favorite: favorite,
language: language,
search: search,
});
export default index;
第四步:配置store
/**
* 自定義log中間件
* https://cn.redux.js.org/docs/advanced/Middleware.html
* @param store
*/
const logger = store => next => action => {
if (typeof action === 'function') {
console.log('dispatching a function');
} else {
console.log('dispatching ', action);
}
const result = next(action);
console.log('nextState ', store.getState());
return result;
};
const middlewares = [
middleware,
logger,
thunk,
];
/**
* 創(chuàng)建store
*/
export default createStore(reducers, applyMiddleware(...middlewares));