在實際的應用開發中,咱們但願作一些異步的(如Ajax請求)且不純粹的操做(如改變外部的狀態),這些在函數式編程範式中被稱爲「反作用」。redux
Redux 的做者將這些反作用的處理經過提供中間件的方式讓開發者自行選擇進行實現。axios
redux-saga 就是用來處理上述反作用(異步任務)的一箇中間件。segmentfault
它是一個接收事件,並可能觸發新事件的過程管理者,爲你的應用管理複雜的流程。api
參考:[React] 12 - Redux: async & middleware架構
需求:"假如當每次 Button 被點擊的時候,咱們想要從給定的 url 中獲取數據"併發
redux-thunk 的主要思想是擴展 action,使得 action 從一個對象變成一個函數。app
採用 redux-thunk, 咱們會這樣寫:
// fetchUrl 返回一個 thunk
function fetchUrl(url) {
return (dispatch) => {
dispatch({
type: 'FETCH_REQUEST'
});
fetch(url).then(data => dispatch({
type: 'FETCH_SUCCESS',
data
}));
}
}
// 若是 thunk 中間件正在運行的話,咱們能夠 dispatch 上述函數以下:
dispatch(
fetchUrl(url)
):
redux-thunk 的缺點
(1)action 雖然擴展了,但所以變得複雜,後期可維護性下降;
(2)thunks 內部測試邏輯比較困難,須要mock全部的觸發函數;
(3)協調併發任務比較困難,當本身的 action 調用了別人的 action,別人的 action 發生改動,則須要本身主動修改;
(4)業務邏輯會散佈在不一樣的地方:啓動的模塊,組件以及thunks內部。
// redux-thunk example
import {applyMiddleware, createStore} from 'redux';
import axios from 'axios';
import thunk from 'redux-thunk';
const initialState = { fetching: false, fetched: false, users: [], error: null }
const reducer = (state = initialState, action) => {
switch(action.type) {
case 'FETCH_USERS_START': {
return {...state, fetching: true}
break;
}
case 'FETCH_USERS_ERROR': {
return {...state, fetching: false, error: action.payload}
break;
}
case 'RECEIVE_USERS': {
return {...state, fetching: false, fetched: true, users: action.payload}
break;
}
}
return state;
}
const middleware = applyMiddleware(thunk);
// store.dispatch({type: 'FOO'}); // redux-thunk 的做用便是將 action: object --> function
store.dispatch((dispatch) => {
dispatch({type: 'FETCH_USERS_START'});
// do something async
axios.get('http://rest.learncode.academy/api/wstern/users')
.then((response) => {
dispatch({type: 'RECEIVE_USERS', payload: response.data})
})
.catch((err) => {
dispatch({type: 'FECTH_USERS_ERROR', payload: err})
})
});
Saga來了
sages 採用 Generator 函數來
yield
Effects(包含指令的文本對象)。
* Generator 函數的做用是能夠暫停執行,再次執行的時候從上次暫停的地方繼續執行。
* Effect 是一個簡單的對象,該對象包含了一些給 middleware 解釋執行的信息。
effects對象的API
例如:
fork
,
call
,
take
,
put
,
cancel
等來建立
Effect。
第一,
// Effect -> 調用 fetch 函數並傳遞 `./products` 做爲參數
{
type: CALL,
function: fetch,
args: ['./products']
}
第二,
與
redux-thunk 不一樣的是,在
redux-saga 中,
UI 組件自身歷來不會觸發任務,
而是會
dispatch
一個 action 來通知在 UI 中哪些地方發生了改變,
而不須要對 action 進行修改。
redux-saga 將
異步任務進行了集中處理,且方便測試。
第三,
sagas 包含3個部分
- worker saga
作全部的工做,如調用 API,進行異步請求,而且得到返回結果
- watcher saga
監聽被 dispatch 的 actions,當接收到 action 或者知道其被觸發時,調用 worker saga 執行任務
- root saga
當即啓動 sagas 的惟一入口
第四,
在
redux-saga 中的基本概念就是:
(1) sagas 自身不真正執行反作用(如函數
call
),可是會構造一個須要執行做用的描述。
(2) 中間件會執行該反作用並把結果返回給 generator 函數。
如何使用Saga
1. 加入 saga 中間件,而且啓動它,它會一直運行
//...
import { createStore, applyMiddleware} from 'redux';
import createSagaMiddleware from 'redux-saga';
import appReducer from './reducers';
//...
const sagaMiddleware = createSagaMiddleware();
const middlewares = [sagaMiddleware];
const store = createStore(appReducer, applyMiddleware(...middlewares));
sagaMiddleware.run(rootSaga);
render(
<Provider store={store}>
<App />
</Provider>,
document.getElementById('app')
);
2. 在 sagas 文件夾中集中寫 saga 文件
import { take, fork, call, put } from 'redux-saga/effects'; // jeff: 都是純函數,每一個函數構造一個特殊的對象
// The worker: perform the requested task
function* fetchUrl(url) {
const data = yield call(fetch, url); // 指示中間件調用 fetch 異步任務 jeff: data是一個相似於 的對象
/**
* 中間件會中止 generator 函數,
* 直到 返回的 被 resolved(或 rejected)
*/
yield put({ type: 'FETCH_SUCCESS', data }); // 指示中間件發起一個 action 到 Store
}
------------------------------------------------------------
// The watcher: watch actions and coordinate worker tasks
function* watchFetchRequests() {
while(true) {
const action = yield take('FETCH_REQUEST'); // 指示中間件等待 Store 上指定的 action,即監聽 action
/**
* 中間件會暫停執行 generator 函數,
* 直到 action 被 dispatch。
*/
yield fork(fetchUrl, action.url); // 指示中間件以無阻塞調用方式執行 fetchUrl
/**
* 告訴中間件去無阻塞調用一個新的 任務,
做爲 函數的參數傳遞。
*/
}
}{type: CALL, function: fetchUrl, args: [url]}fetchPromisewacthFetchRequestsFETCH_REQUESTfetchUrl* action.urlfetchUrl
總結領悟:
JavaScript 是單線程的,redux-saga
讓事情看起來是同時進行的。
架構上的優點:
將全部的異步流程控制都移入到了 sagas,UI 組件不用執行業務邏輯,只需 dispatch action 就行,加強組件複用性。
結合具體使用場景:登陸
export function* loginSaga() {
while(true) {
const { user, pass } = yield take(LOGIN_REQUEST) //等待 Store 上指定的 action LOGIN_REQUEST
try {
let { data } = yield call(request.post, '/login', { user, pass }); // 阻塞,請求後臺數據
yield fork(loadUserData, data.uid); // 非阻塞執行loadUserData
yield put({ type: LOGIN_SUCCESS, data }); // 發起一個action,相似於dispatch
} catch(error) {
yield put({ type: LOGIN_ERROR, error });
}
}
}
export function* loadUserData(uid) {
try {
yield put({ type: USERDATA_REQUEST });
let { data } = yield call(request.get, `/users/${uid}`);
yield put({ type: USERDATA_SUCCESS, data });
} catch(error) {
yield put({ type: USERDATA_ERROR, error });
}
}
提出一個問題:
Firebase提供的登陸接口爲什麼使用起來簡單許多,而以上這些方案卻相對複雜了許多?