在实际的应用开发中,咱们但愿作一些异步的(如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提供的登陆接口为什么使用起来简单许多,而以上这些方案却相对复杂了许多?