序言
這里要講的就是一個(gè)Redux在React中的應(yīng)用問(wèn)題,講一講Redux况鸣,react-redux牢贸,redux-thunk,redux-actions镐捧,redux-promise潜索,redux-saga這些包的作用和他們解決的問(wèn)題。
因?yàn)椴幌氚哑锰L(zhǎng)懂酱,所以沒(méi)有太多源碼分析和語(yǔ)法講解竹习,能怎么簡(jiǎn)單就怎么簡(jiǎn)單。
Redux
先看看百度百科上面Redux的一張圖:
這是Redux在Github上的介紹:Redux用于js程序列牺,是一個(gè)可預(yù)測(cè)的狀態(tài)容器整陌。
在這里我們首先要明白的是什么叫可預(yù)測(cè)?什么叫狀態(tài)容器瞎领?
什么叫狀態(tài)泌辫?實(shí)際上就是變量,對(duì)話框顯示或隱藏的變量九默,一杯奶茶多少錢(qián)的變量震放。
那么這個(gè)狀態(tài)容器,實(shí)際上就是一個(gè)存放這些變量的變量驼修。
你創(chuàng)建了一個(gè)全局變量叫Store殿遂,然后將代碼中控制各個(gè)狀態(tài)的變量存放在里面诈铛,那么現(xiàn)在Store就叫做狀態(tài)容器。
什么叫可預(yù)測(cè)墨礁?
你在操作這個(gè)Store的時(shí)候癌瘾,總是用Store.price的方式來(lái)設(shè)置值,這種操作數(shù)據(jù)的方式很原始饵溅,對(duì)于復(fù)雜的系統(tǒng)而言永遠(yuǎn)都不知道程序在運(yùn)行的過(guò)程中發(fā)生了什么妨退。
那么現(xiàn)在我們都通過(guò)發(fā)送一個(gè)Action去做修改,而Store在接收到Action后會(huì)使用Reducer對(duì)Action傳遞的數(shù)據(jù)做處理蜕企,最后應(yīng)用到Store中咬荷。
相對(duì)于Store.price的方式來(lái)修改者,這種方式無(wú)疑更麻煩轻掩,但是這種方式的好處就是幸乒,每一個(gè)Action里面都可以寫(xiě)日志,可以記錄各種狀態(tài)的變動(dòng)唇牧,這就是可預(yù)測(cè)罕扎。
所以如果你的程序很簡(jiǎn)單,你完全沒(méi)有必要去用Redux丐重。
看看Redux的示例代碼:
actionTypes.js:
export const CHANGE_BTN_TEXT = 'CHANGE_BTN_TEXT';
actions.js:
import * as T from './actionTypes';
export const changeBtnText = (text) => {
return {
type: T.CHANGE_BTN_TEXT,
payload: text
};
};
reducers.js:
import * as T from './actionTypes';
const initialState = {
btnText: '我是按鈕',
};
const pageMainReducer = (state = initialState, action) => {
switch (action.type) {
case T.CHANGE_BTN_TEXT:
return {
...state,
btnText: action.payload
};
default:
return state;
}
};
export default pageMainReducer;
index.js
import { createStore } from 'redux';
import reducer from './reducers';
import { changeBtnText } from './actions';
const store = createStore(reducer);
// 開(kāi)始監(jiān)聽(tīng)腔召,每次state更新,那么就會(huì)打印出當(dāng)前狀態(tài)
const unsubscribe = store.subscribe(() => {
console.info(store.getState());
});
// 發(fā)送消息
store.dispatch(changeBtnText('點(diǎn)擊了按鈕'));
// 停止監(jiān)聽(tīng)state的更新
unsubscribe();
這里就不解釋什么語(yǔ)法作用了扮惦,網(wǎng)上這樣的資料太多了臀蛛。
Redux與React的結(jié)合:react-redux
Redux是一個(gè)可預(yù)測(cè)的狀態(tài)容器,跟React這種構(gòu)建UI的庫(kù)是兩個(gè)相互獨(dú)立的東西崖蜜。
Redux要應(yīng)用到React中浊仆,很明顯action,reducer豫领,dispatch這幾個(gè)階段并不需要改變抡柿,唯一需要考慮的是redux中的狀態(tài)需要如何傳遞給react組件。
很簡(jiǎn)單等恐,只需要每次要更新數(shù)據(jù)時(shí)運(yùn)用store.getState獲取到當(dāng)前狀態(tài)洲劣,并將這些數(shù)據(jù)傳遞給組件即可。
那么問(wèn)題來(lái)了鼠锈,如何讓每個(gè)組件都獲取到store呢闪檬?
當(dāng)然是將store作為一個(gè)值傳遞給根組件,然后store就會(huì)一級(jí)一級(jí)往下傳购笆,使得每個(gè)組件都能獲取到store的值。
但是這樣太繁瑣了虚循,難道每個(gè)組件需要寫(xiě)一個(gè)傳遞store的邏輯同欠?為了解決這個(gè)問(wèn)題样傍,那么得用到React的context玩法,通過(guò)在根組件上將store放在根組件的context中铺遂,然后在子組件中通過(guò)context獲取到store。
react-redux的主要思路也是如此,通過(guò)嵌套組件Provider將store放到context中毙芜,通過(guò)connect這個(gè)高階組件娄柳,來(lái)隱藏取store的操作,這樣我們就不需要每次去操作context寫(xiě)一大堆代碼那么麻煩了粮坞。
然后我們?cè)賮?lái)基于之前的Redux示例代碼給出react-redux的使用演示代碼蚊荣,其中action和reduce部分不變,先增加一個(gè)組件PageMain:
const PageMain = (props) => {
return (
<div>
<button onClick={() => {
props.changeText('按鈕被點(diǎn)擊了');
}}
>
{props.btnText}
</button>
</div>
);
};
// 映射store.getState()的數(shù)據(jù)到PageMain
const mapStateToProps = (state) => {
return {
btnText: state.pageMain.btnText,
};
};
// 映射使用了store.dispatch的函數(shù)到PageMain
const mapDispatchToProps = (dispatch) => {
return {
changeText: (text) => {
dispatch(changeBtnText(text));
}
};
};
// 這個(gè)地方也可以簡(jiǎn)寫(xiě)莫杈,react-redux會(huì)自動(dòng)做處理
const mapDispatchToProps = {
changeText: changeBtnText
};
export default connect(mapStateToProps, mapDispatchToProps)(PageMain);
注意上面的state.pageMain.btnText互例,這個(gè)pageMain是我用redux的combineReducers將多個(gè)reducer合并后給的原先的reducer一個(gè)命名。
它的代碼如下:
import { combineReducers } from 'redux';
import pageMain from './components/pageMain/reducers';
const reducer = combineReducers({
pageMain
});
export default reducer;
然后修改index.js:
import React from 'react';
import { createStore } from 'redux';
import { Provider } from 'react-redux';
import ReactDOM from 'react-dom';
import reducer from './reducers';
import PageMain from './components/pageMain';
const store = createStore(reducer);
const App = () => (
<Provider store={store}>
<PageMain />
</Provider>
);
ReactDOM.render(<App />, document.getElementById('app'));
Redux的中間件
之前我們講到Redux是個(gè)可預(yù)測(cè)的狀態(tài)容器筝闹,這個(gè)可預(yù)測(cè)在于對(duì)數(shù)據(jù)的每一次修改都可以進(jìn)行相應(yīng)的處理和記錄媳叨。
假如現(xiàn)在我們需要在每次修改數(shù)據(jù)時(shí),記錄修改的內(nèi)容关顷,我們可以在每一個(gè)dispatch前面加上一個(gè)console.info記錄修改的內(nèi)容糊秆。
但是這樣太繁瑣了,所以我們可以直接修改store.dispatch:
let next = store.dispatch
store.dispatch = (action)=> {
console.info('修改內(nèi)容為:', action)
next(action)
}
Redux中也有同樣的功能议双,那就是applyMiddleware扩然。直譯過(guò)來(lái)就是“應(yīng)用中間件”,它的作用就是改造dispatch函數(shù)聋伦,跟上面的玩法基本雷同夫偶。
來(lái)一段演示代碼:
import { createStore, applyMiddleware } from 'redux';
import reducer from './reducers';
const store = createStore(reducer, applyMiddleware(curStore => next => action => {
console.info(curStore.getState(), action);
return next(action);
}));
看起來(lái)挺奇怪的玩法,但是理解起來(lái)并不難觉增。通過(guò)這種返回函數(shù)的方法兵拢,使得applyMiddleware內(nèi)部以及我們使用時(shí)可以處理store和action,并且這里next的應(yīng)用就是為了使用多個(gè)中間件而存在的逾礁。
而通常我們沒(méi)有必要自己寫(xiě)中間件说铃,比如日志的記錄就已經(jīng)有了成熟的中間件:redux-logger,這里給一個(gè)簡(jiǎn)單的例子:
import { applyMiddleware, createStore } from 'redux';
import createLogger from 'redux-logger';
import reducer from './reducers';
const logger = createLogger();
const store = createStore(
reducer,
applyMiddleware(logger)
);
這樣就可以記錄所有action及其發(fā)送前后的state的日志嘹履,我們可以了解到代碼實(shí)際運(yùn)行時(shí)到底發(fā)生了什么腻扇。
redux-thunk:處理異步action
在上面的代碼中,我們點(diǎn)擊按鈕后砾嫉,直接修改了按鈕的文本幼苛,這個(gè)文本是個(gè)固定的值。
actions.js:
import * as T from './actionTypes';
export const changeBtnText = (text) => {
return {
type: T.CHANGE_BTN_TEXT,
payload: text
};
};
但是在我們實(shí)際生產(chǎn)的過(guò)程中焕刮,很多情況都是需要去請(qǐng)求服務(wù)端拿到數(shù)據(jù)再修改的舶沿,這個(gè)過(guò)程是一個(gè)異步的過(guò)程墙杯。又或者需要setTimeout去做一些事情。
我們可以去修改這一部分如下:
const mapDispatchToProps = (dispatch) => {
return {
changeText: (text) => {
dispatch(changeBtnText('正在加載中'));
axios.get('http://test.com').then(() => {
dispatch(changeBtnText('加載完畢'));
}).catch(() => {
dispatch(changeBtnText('加載有誤'));
});
}
};
};
實(shí)際上括荡,我們每天不知道要處理多少這樣的代碼高镐。
但是問(wèn)題來(lái)了,異步操作相比同步操作多了一個(gè)很多確定因素畸冲,比如我們展示正在加載中時(shí)嫉髓,可能要先要做異步操作A,而請(qǐng)求后臺(tái)的過(guò)程卻非骋叵校快算行,導(dǎo)致加載完畢先出現(xiàn),而這時(shí)候操作A才做完监憎,然后再展示加載中纱意。
所以上面的這個(gè)玩法并不能滿(mǎn)足這種情況。
這個(gè)時(shí)候我們需要去通過(guò)store.getState獲取當(dāng)前狀態(tài)鲸阔,從而判斷到底是展示正在加載中還是展示加載完畢偷霉。
這個(gè)過(guò)程就不能放在mapDispatchToProps中了,而需要放在中間件中褐筛,因?yàn)橹虚g件中可以拿到store类少。
首先創(chuàng)造store的時(shí)候需要應(yīng)用react-thunk,也就是
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import reducer from './reducers';
const store = createStore(
reducer,
applyMiddleware(thunk)
);
它的源碼超級(jí)簡(jiǎn)單:
function createThunkMiddleware(extraArgument) {
return ({ dispatch, getState }) => next => action => {
if (typeof action === 'function') {
return action(dispatch, getState, extraArgument);
}
return next(action);
};
}
const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;
export default thunk;
從這個(gè)里面可以看出渔扎,它就是加強(qiáng)了dispatch的功能硫狞,在dispatch一個(gè)action之前,去判斷action是否是一個(gè)函數(shù)晃痴,如果是函數(shù)残吩,那么就執(zhí)行這個(gè)函數(shù)。
那么我們使用起來(lái)就很簡(jiǎn)單了倘核,此時(shí)我們修改actions.js
import axios from 'axios';
import * as T from './actionTypes';
export const changeBtnText = (text) => {
return {
type: T.CHANGE_BTN_TEXT,
payload: text
};
};
export const changeBtnTextAsync = (text) => {
return (dispatch, getState) => {
if (!getState().isLoading) {
dispatch(changeBtnText('正在加載中'));
}
axios.get(`http://test.com/${text}`).then(() => {
if (getState().isLoading) {
dispatch(changeBtnText('加載完畢'));
}
}).catch(() => {
dispatch(changeBtnText('加載有誤'));
});
};
};
而原來(lái)mapDispatchToProps中的玩法和同步action的玩法是一樣的:
const mapDispatchToProps = (dispatch) => {
return {
changeText: (text) => {
dispatch(changeBtnTextAsync(text));
}
};
};
通過(guò)redux-thunk我們可以簡(jiǎn)單地進(jìn)行異步操作泣侮,并且可以獲取到各個(gè)異步操作時(shí)期狀態(tài)的值。
redux-actions:簡(jiǎn)化redux的使用
Redux雖然好用紧唱,但是里面還是有些重復(fù)代碼活尊,所以有了redux-actions來(lái)簡(jiǎn)化那些重復(fù)代碼。
這部分簡(jiǎn)化工作主要集中在構(gòu)造action和處理reducers方面漏益。
先來(lái)看看原先的actions
import axios from 'axios';
import * as T from './actionTypes';
export const changeBtnText = (text) => {
return {
type: T.CHANGE_BTN_TEXT,
payload: text
};
};
export const changeBtnTextAsync = () => {
return (dispatch, getState) => {
if (!getState().isLoading) {
dispatch(changeBtnText('正在加載中'));
}
axios.get('http://test.com').then(() => {
if (getState().isLoading) {
dispatch(changeBtnText('加載完畢'));
}
}).catch(() => {
dispatch(changeBtnText('加載有誤'));
});
};
};
然后再來(lái)看看修改后的:
import axios from 'axios';
import * as T from './actionTypes';
import { createAction } from 'redux-actions';
export const changeBtnText = createAction(T.CHANGE_BTN_TEXT, text => text);
export const changeBtnTextAsync = () => {
return (dispatch, getState) => {
if (!getState().isLoading) {
dispatch(changeBtnText('正在加載中'));
}
axios.get('http://test.com').then(() => {
if (getState().isLoading) {
dispatch(changeBtnText('加載完畢'));
}
}).catch(() => {
dispatch(changeBtnText('加載有誤'));
});
};
};
這一塊代碼替換上面的部分代碼后蛹锰,程序運(yùn)行結(jié)果依然保持不變,也就是說(shuō)createAction只是對(duì)上面的代碼進(jìn)行了簡(jiǎn)單的封裝而已绰疤。
這里注意到铜犬,異步的action就不要用createAction,因?yàn)檫@個(gè)createAction返回的是一個(gè)對(duì)象,而不是一個(gè)函數(shù)翎苫,就會(huì)導(dǎo)致redux-thunk的代碼沒(méi)有起到作用权埠。
這里也可以使用createActions這個(gè)函數(shù)同時(shí)創(chuàng)建多個(gè)action榨了,但是講道理煎谍,這個(gè)語(yǔ)法很奇怪,用createAction就好龙屉。
同樣redux-actions對(duì)reducer的部分也進(jìn)行了處理呐粘,比如handleAction以及handelActions。
先來(lái)看看原先的reducers
import * as T from './actionTypes';
const initialState = {
btnText: '我是按鈕',
};
const pageMainReducer = (state = initialState, action) => {
switch (action.type) {
case T.CHANGE_BTN_TEXT:
return {
...state,
btnText: action.payload
};
default:
return state;
}
};
export default pageMainReducer;
然后使用handleActions來(lái)處理
import { handleActions } from 'redux-actions';
import * as T from './actionTypes';
const initialState = {
btnText: '我是按鈕',
};
const pageMainReducer = handleActions({
[T.CHANGE_BTN_TEXT]: {
next(state, action) {
return {
...state,
btnText: action.payload,
};
},
throw(state) {
return state;
},
},
}, initialState);
export default pageMainReducer;
這里handleActions可以加入異常處理转捕,并且?guī)椭幚砹顺跏贾怠?/p>
注意作岖,無(wú)論是createAction還是handleAction都只是對(duì)代碼做了一點(diǎn)簡(jiǎn)單的封裝,兩者可以單獨(dú)使用五芝,并不是說(shuō)使用了createAction就必須要用handleAction痘儡。
redux-promise:redux-actions的好基友,輕松創(chuàng)建和處理異步action
還記得上面在使用redux-actions的createAction時(shí)枢步,我們對(duì)異步的action無(wú)法處理沉删。
因?yàn)槲覀兪褂胏reateAction后返回的是一個(gè)對(duì)象,而不是一個(gè)函數(shù)醉途,就會(huì)導(dǎo)致redux-thunk的代碼沒(méi)有起到作用矾瑰。
而現(xiàn)在我們將使用redux-promise來(lái)處理這類(lèi)情況。
可以看看之前我們使用 createAction的例子:
export const changeBtnText = createAction(T.CHANGE_BTN_TEXT, text => text);
現(xiàn)在我們先加入redux-promise中間件:
import thunk from 'redux-thunk';
import createLogger from 'redux-logger';
import promiseMiddleware from 'redux-promise';
import reducer from './reducers';
const store = createStore(reducer, applyMiddleware(thunk, createLogger, promiseMiddleware));
然后再處理異步action:
export const changeBtnTextAsync = createAction(T.CHANGE_BTN_TEXT_ASYNC, (text) => {
return axios.get(`http://test.com/${text}`);
});
可以看到我們這里返回的是一個(gè)Promise對(duì)象.(axios的get方法結(jié)果就是Promise對(duì)象)
我們還記得redux-thunk中間件隘擎,它會(huì)去判斷action是否是一個(gè)函數(shù)殴穴,如果是就執(zhí)行。
而我們這里的redux-promise中間件货葬,他會(huì)在dispatch時(shí)采幌,判斷如果action不是類(lèi)似
{
type:'',
payload: ''
}
這樣的結(jié)構(gòu),也就是 FSA,那么就去判斷是否為promise對(duì)象震桶,如果是就執(zhí)行action.then的玩法休傍。
很明顯,我們createAction后的結(jié)果是FSA尼夺,所以會(huì)走下面這個(gè)分支尊残,它會(huì)去判斷action.payload是否為promise對(duì)象,是的話那就
action.payload
.then(result => dispatch({ ...action, payload: result }))
.catch(error => {
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
})
也就是說(shuō)我們的代碼最后會(huì)轉(zhuǎn)變?yōu)椋?/p>
axios.get(`http://test.com/${text}`)
.then(result => dispatch({ ...action, payload: result }))
.catch(error => {
dispatch({ ...action, payload: error, error: true });
return Promise.reject(error);
})
這個(gè)中間件的代碼也很簡(jiǎn)單淤堵,總共19行寝衫,大家可以在github上直接看看。
redux-saga:控制器與更優(yōu)雅的異步處理
我們的異步處理用的是redux-thunk + redux-actions + redux-promise拐邪,其實(shí)用起來(lái)還是蠻好用的慰毅。
但是隨著ES6中Generator的出現(xiàn),人們發(fā)現(xiàn)用Generator處理異步可以更簡(jiǎn)單扎阶。
而redux-saga就是用Generator來(lái)處理異步汹胃。
以下講的知識(shí)是基于Generator的婶芭,如果您對(duì)這個(gè)不甚了解,可以簡(jiǎn)單了解一下相關(guān)知識(shí)着饥,大概需要2分鐘時(shí)間犀农,并不難。
redux-saga文檔并沒(méi)有說(shuō)自己是處理異步的工具宰掉,而是說(shuō)用來(lái)處理邊際效應(yīng)(side effects)呵哨,這里的邊際效應(yīng)你可以理解為程序?qū)ν獠康牟僮鳎热缯?qǐng)求后端轨奄,比如操作文件孟害。
redux-saga同樣是一個(gè)redux中間件,它的定位就是通過(guò)集中控制action挪拟,起到一個(gè)類(lèi)似于MVC中控制器的效果挨务。
同時(shí)它的語(yǔ)法使得復(fù)雜異步操作不會(huì)像promise那樣出現(xiàn)很多then的情況,更容易進(jìn)行各類(lèi)測(cè)試玉组。
這個(gè)東西有它的好處谎柄,同樣也有它不好的地方,那就是比較復(fù)雜球切,有一定的學(xué)習(xí)成本谷誓。
并且我個(gè)人而言很不習(xí)慣Generator的用法,覺(jué)得Promise或者await更好用吨凑。
這里還是記錄一下用法捍歪,畢竟有很多框架都用到了這個(gè)。
應(yīng)用這個(gè)中間件和我們的其他中間件沒(méi)有區(qū)別:
import React from 'react';
import { createStore, applyMiddleware } from 'redux';
import promiseMiddleware from 'redux-promise';
import createSagaMiddleware from 'redux-saga';
import {watchDelayChangeBtnText} from './sagas';
import reducer from './reducers';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(reducer, applyMiddleware(promiseMiddleware, sagaMiddleware));
sagaMiddleware.run(watchDelayChangeBtnText);
創(chuàng)建sage中間件后鸵钝,然后再將其中間件接入到store中,最后需要用中間件運(yùn)行sages.js返回的Generator,監(jiān)控各個(gè)action糙臼。
現(xiàn)在我們給出sages.js的代碼:
import { delay } from 'redux-saga';
import { put, call, takeEvery } from 'redux-saga/effects';
import * as T from './components/pageMain/actionTypes';
import { changeBtnText } from './components/pageMain/actions';
const consoleMsg = (msg) => {
console.info(msg);
};
/**
* 處理編輯效應(yīng)的函數(shù)
*/
export function* delayChangeBtnText() {
yield delay(1000);
yield put(changeBtnText('123'));
yield call(consoleMsg, '完成改變');
}
/**
* 監(jiān)控Action的函數(shù)
*/
export function* watchDelayChangeBtnText() {
yield takeEvery(T.WATCH_CHANGE_BTN_TEXT, delayChangeBtnText);
}
在redux-saga中有一類(lèi)用來(lái)處理邊際效應(yīng)的函數(shù)比如put、call恩商,它們的作用是為了簡(jiǎn)化操作变逃。
比如put相當(dāng)于redux的dispatch的作用,而call相當(dāng)于調(diào)用函數(shù)怠堪。(可以參考上面代碼中的例子)
還有另一類(lèi)函數(shù)就是類(lèi)似于takeEvery揽乱,它的作用就是和普通redux中間件一樣攔截到action后作出相應(yīng)處理。
比如上面的代碼就是攔截到T.WATCH_CHANGE_BTN_TEXT這個(gè)類(lèi)型的action粟矿,然后調(diào)用delayChangeBtnText凰棉。
然后可以回看我們之前的代碼,有這么一行代碼:
sagaMiddleware.run(watchDelayChangeBtnText);
這里實(shí)際就是引入監(jiān)控的這個(gè)生成器后陌粹,再運(yùn)行監(jiān)控生成器撒犀。
這樣我們?cè)诖a里面dispatch類(lèi)型為T(mén).WATCH_CHANGE_BTN_TEXT的action時(shí)就會(huì)被攔截然后做出相應(yīng)處理。
當(dāng)然這里有人可能會(huì)提出疑問(wèn),難道每一個(gè)異步都要這么寫(xiě)嗎或舞,那豈不是要run很多次荆姆?
當(dāng)然不是這個(gè)樣子,我們可以在sage中這么寫(xiě):
export default function* rootSaga() {
yield [
watchDelayChangeBtnText()映凳,
watchOtherAction()
]
}
我們只需要按照這個(gè)格式去寫(xiě)胆筒,將watchDelayChangeBtnText這樣用于監(jiān)控action的生成器放在上面那個(gè)代碼的數(shù)組中,然后作為一個(gè)生成器返回魏宽。
現(xiàn)在只需要引用這個(gè)rootSaga即可腐泻,然后run這個(gè)rootSaga决乎。
以后如果要監(jiān)控更多的action队询,只需要在sages.js中加上新的監(jiān)控的生成器即可。
通過(guò)這樣的處理构诚,我們就將sages.js做成了一個(gè)像MVC中的控制器的東西蚌斩,可以用來(lái)處理各種各樣的action,處理復(fù)雜的異步操作和邊際效應(yīng)范嘱。
但是這里要注意,一定要加以區(qū)分sages.js中使用監(jiān)控的action和真正功能用的action送膳,比如加個(gè)watch關(guān)鍵字,以免業(yè)務(wù)復(fù)雜后代碼混亂丑蛤。
總結(jié)
總的來(lái)說(shuō):
- redux是一個(gè)可預(yù)測(cè)的狀態(tài)容器叠聋,
- react-redux是將store和react結(jié)合起來(lái),使得數(shù)據(jù)展示和修改對(duì)于react項(xiàng)目而言更簡(jiǎn)單
- redux中間件就是在dispatch action前對(duì)action做一些處理
- redux-thunk用于對(duì)異步做操作
- redux-actions用于簡(jiǎn)化redux操作
- redux-promise可以配合redux-actions用來(lái)處理Promise對(duì)象受裹,使得異步操作更簡(jiǎn)單
- redux-saga可以起到一個(gè)控制器的作用碌补,集中處理邊際效用,并使得異步操作的寫(xiě)法更優(yōu)雅棉饶。