依照慣例袖外,開頭先放出redux中文文檔地址
異步場景的基本思路
之前討論的所有場景都是同步場景曼验,實際開發(fā)中肯定有很多異步場景,下面討論一下異步場景的問題鬓照。
首先豺裆,異步場景和同步場景本質(zhì)上還是利用action
、reducer
躺酒、store
這些東西蔑歌,只是設計reducer
和action
的時候更加巧妙,考慮到了異步狀態(tài)园匹。
對應不同的action
,在reducer
中添加專門表現(xiàn)異步狀態(tài)的內(nèi)容煞烫。
function post(
state = {
isFetching: false,
items: []
},
action
) {
switch(action.type) {
case REQUEST:
return Object.assign({}, state, {
isFetching: true,
})
case RECEIVE:
return Object.assign({}, state, {
isFetching: false,
items: action.posts
})
default:
return state
}
}
搞定了reducer
還要考慮異步場景的action
滞详,我們先不仔細探究代碼實現(xiàn)紊馏,只要想明白如果store.dispatch(someAction)
,然后按照異步回調(diào)的順序觸發(fā)上面的reducer
岸啡,那么異步場景就實現(xiàn)了赫编。
解決異步場景
上面我們籠統(tǒng)地講了異步場景的實現(xiàn)思路,目前看來reducer
部分已經(jīng)沒有大問題和同步場景十分類似悦荒,action
部分和同步場景有所區(qū)別搬味。
簡單思考可以發(fā)現(xiàn)蟀拷,我們需要在action creator
函數(shù)中實現(xiàn)異步,即當我們調(diào)用一個action creator
時问芬,不是簡單需要返回一個action
對象,而是根據(jù)異步的場景返回不同的action
强戴】蝗幔看起來我們需要改造action
。
理解了這個問題陵刹,就找到了異步場景的癥結(jié)衰琐。
redux
有許多配套的中間件(middleware
),關于中間件的概念可以查看上面的文檔羡宙,我們不仔細討論狗热。簡要地說,中間件在調(diào)用action creator
后起作用僧凰,可以有多個中間件存在训措,數(shù)據(jù)流會在中間件之間傳遞光羞,數(shù)據(jù)流經(jīng)過所有中間件處理流出時應該還是一個簡單的action
對象纱兑,到達store.dispatch(action)
時所有內(nèi)容應該和同步數(shù)據(jù)流一致。
redux-thunk 實現(xiàn)
redux-thunk
改造了store.dispatch
总珠,使action creator
能接收一個函數(shù)作為參數(shù)。改造完action creator
之后钓瞭,就可以創(chuàng)造一個內(nèi)部根據(jù)狀態(tài)再次異步操作的action
山涡。
const fetchPosts = postTitle => (dispatch, getState) => {
dispatch(requestPosts(postTitle));
return fetch(`/some/API/${postTitle}.json`)
.then(response => response.json())
.then(json => dispatch(receivePosts(postTitle, json)));
};
};
結(jié)合一個小例子來看一下
已有一個container
組件
class example extends Component {
constructor() {
super();
}
componentDidMount() {
組件需要調(diào)用后端API載入數(shù)據(jù)
this.props.getName();
}
render () {
console.log(this.props)
return (
<div>
// 組件按照不同狀態(tài)顯示‘querying...’/正常數(shù)據(jù)
{ this.props.info.server.isFetching ?
(
<span>
querying...
</span>
)
:
(
// 點擊請求API刷新狀態(tài)
<span onClick={() => this.props.getName()}>
{ this.props.info.server.name }
</span>
)
}
...
</div>
);
}
}
const mapStateToProps = (state) => {
return {
info: state.info
}
};
const mapDispatchToProps = (dispatch) => {
return {
getName: () => {
dispatch(getServerInfo());
}
};
};
const Example = connect(
mapStateToProps,
mapDispatchToProps
)(example);
export default Example;
下面開始實現(xiàn)這個小功能
編寫對應的action
function fetchServerInfo() {
return {
type: 'GET SERVER INFO'
};
}
function receiveServerInfo({ payload: server }) {
return {
type: 'RECEIVE SERVER INFO',
payload: server
};
}
// 這是一個改造后的action creator竞穷,可以異步dispatch(action)
function getServerInfo() {
return (dispatch, getState) => {
dispatch(fetchServerInfo());
return fetch(`http://server/info`)
.then(
response => response.json(),
error => console.log('an error', error)
)
.then(
(json) => {
let { server } = json;
dispatch(receiveServerInfo({ payload: server }));
}
)
}
}
編寫reducer
// 加入一個isFetching的狀態(tài)標志位
const initialInfo = {
server: {
isFetching: false,
name: 'localhost',
status: ''
}
};
function setServerName (state = initialInfo, action) {
let server = state.server;
switch (action.type) {
case 'GET SERVER INFO':
return Object.assign(
{},
server,
{
isFetching: true
}
);
case 'RECEIVE SERVER INFO':
let { payload: { name } } = action;
return Object.assign(
{},
server,
{
isFetching: false,
name
}
);
default:
return server
}
}
function info (state = initialInfo, action) {
return {
server: setServerName(state.title, action)
}
}
export default info;
添加middleware
import { createStore, applyMiddleware } from 'redux';
import thunkMiddleWare from 'redux-thunk';
...
const store = createStore(
reducer,
applyMiddleware(
thunkMiddleWare
)
);
redux-saga 實現(xiàn)
redux-saga
是一種不一樣的實現(xiàn)方式瘾带,它沒有在action creator
上做文章看政,它監(jiān)聽了action
,如果action
中有effect
于颖,那么它可以調(diào)用Genertor
函數(shù)實現(xiàn)異步。
還是結(jié)合上面的小例子來看一下:
已有一個container
組件
class example extends Component {
constructor() {
super();
}
componentDidMount() {
組件需要調(diào)用后端API載入數(shù)據(jù)
this.props.getName();
}
render () {
console.log(this.props)
return (
<div>
// 組件按照不同狀態(tài)顯示‘querying...’/正常數(shù)據(jù)
{ this.props.info.server.isFetching ?
(
<span>
querying...
</span>
)
:
(
// 點擊請求API刷新狀態(tài)
<span onClick={() => this.props.getName()}>
{ this.props.info.server.name }
</span>
)
}
...
</div>
);
}
}
const mapStateToProps = (state) => {
return {
info: state.info
}
};
const mapDispatchToProps = (dispatch) => {
return {
getName: () => {
dispatch(getServerInfo());
}
};
};
const Example = connect(
mapStateToProps,
mapDispatchToProps
)(example);
export default Example;
創(chuàng)建一個saga
文件監(jiān)聽action
import { takeEvery } from 'redux-saga';
import { call, put } from 'redux-saga/effects';
import requestServerInfo from '../../services/info'
// 遇到被監(jiān)聽的action會調(diào)用下面的函數(shù),并將異步操作分解
function* getServerInfo() {
try {
yield put({ type: 'GET SERVER INFO' });
const { data: { server: server } } = yield call(requestServerInfo, 'http://server/test');
yield put({ type: 'RECEIVE SERVER INFO', payload: { server } });
} catch (e) {
yield put({ type: 'RECEIVE AN ERROR' });
}
}
// 在此處使用takeEvery監(jiān)聽action
function* infoSaga() {
yield* takeEvery('QUERY_INFO', getServerInfo);
}
export { infoSaga };
在store
中應用saga
import { createStore, applyMiddleware } from 'redux';
import createSagaMiddleware from 'redux-saga';
import { infoSaga } from '../sagas/info';
import reducer from '../reducers/all';
// 引入redux-saga中間件
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
reducer,
applyMiddleware(
sagaMiddleware
)
);
sagaMiddleware.run(infoSaga);
export default store;
剩下reducer
部分和上面一樣處理加入isFetching
標志位即可章母,沒有變化