React在Github上已经有接近70000的 star 数了,是目前最热门的前端框架。而我学习React也有一段时间了,现在就开始用 React+Redux 进行实战!
上回说到使用Redux进行状态管理,这次我们使用Redux-saga 管理 Redux 应用异步操作
React 实践项目 (一)
React 实践项目 (二)
React 实践项目 (三)
- 首先我们来看看登陆的 Reducer
export const auth = (state = initialState,action = {}) => { switch (action.type) { case LOGIN_USER: return state.merge({ 'user': action.data,'error': null,'token': null,}); case LOGIN_USER_SUCCESS: return state.merge({ 'token': action.data,'error': null }); case LOGIN_USER_FAILURE: return state.merge({ 'token': null,'error': action.data }); default: return state } };
Sagas 监听发起的 action,然后决定基于这个 action 来做什么:是发起一个异步调用(比如一个 Ajax 请求),还是发起其他的 action 到 Store,甚至是调用其他的 Sagas。
具体到这个登陆功能就是我们在登陆弹窗点击登陆时会发出一个 LOGIN_USER
action,Sagas 监听到 LOGIN_USER
action,发起一个 Ajax 请求到后台,根据结果决定发起 LOGIN_USER_SUCCESS
action 还是LOGIN_USER_FAILURE
action
接下来,我们来实现这个流程
创建 Saga middleware 连接至 Redux store
在 package.json 中添加 redux-saga
依赖
"redux-saga": "^0.15.4"
修改 src/redux/store/store.js
/** * Created by Yuicon on 2017/6/27. */ import {createStore,applyMiddleware } from 'redux'; import createSagaMiddleware from 'redux-saga' import reducer from '../reducer/reducer'; import rootSaga from '../sagas/sagas'; const sagaMiddleware = createSagaMiddleware(); const store = createStore( reducer,applyMiddleware(sagaMiddleware) ); sagaMiddleware.run(rootSaga); export default store;
监听 action
创建 src/redux/sagas/sagas.js
/** * Created by Yuicon on 2017/6/28. */ import { takeLatest } from 'redux-saga/effects'; import {registerUserAsync,loginUserAsync} from './users'; import {REGISTER_USER,LOGIN_USER} from '../action/users'; export default function* rootSaga() { yield [ takeLatest(REGISTER_USER,registerUserAsync),takeLatest(LOGIN_USER,loginUserAsync) ]; }
我们可以看到在 rootSaga 中监听了两个 action 登陆和注册 。
在上面的例子中,takeLatest 只允许执行一个 loginUserAsync 任务。并且这个任务是最后被启动的那个。 如果之前已经有一个任务在执行,那之前的这个任务会自动被取消。
如果我们允许多个 loginUserAsync 实例同时启动。在某个特定时刻,我们可以启动一个新 loginUserAsync 任务, 尽管之前还有一个或多个 loginUserAsync 尚未结束。我们可以使用 takeEvery 辅助函数。
发起一个 Ajax 请求
获取 Store state 上的数据
selectors.js
/** * Created by Yuicon on 2017/6/28. */ export const getAuth = state => state.auth;
api
api.js
/** * Created by Yuicon on 2017/7/4. * https://github.com/Yuicon */ /** * 这是我自己的后台服务器,用 Java 实现 * 项目地址:https://github.com/DigAg/digag-server * 文档:http://139.224.135.86:8080/swagger-ui.html#/ */ const getURL = (url) => `http://139.224.135.86:8080/${url}`; export const login = (user) => { return fetch(getURL("auth/login"),{ method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify(user) }).then(response => response.json()) .then(json => { return json; }) .catch(ex => console.log('parsing Failed',ex)); };
创建 src/redux/sagas/users.js
/** * Created by Yuicon on 2017/6/30. */ import {select,put,call} from 'redux-saga/effects'; import {getAuth,getUsers} from './selectors'; import {loginSuccessAction,loginFailureAction,registerSuccessAction,registerFailureAction} from '../action/users'; import {login,register} from './api'; import 'whatwg-fetch'; export function* loginUserAsync() { // 获取Store state 上的数据 const auth = yield select(getAuth); const user = auth.get('user'); // 发起 ajax 请求 const json = yield call(login.bind(this,user),'login'); if (json.success) { localStorage.setItem('token',json.data); // 发起 loginSuccessAction yield put(loginSuccessAction(json.data)); } else { // 发起 loginFailureAction yield put(loginFailureAction(json.error)); } }
select(selector,...args)
用于获取Store state 上的数据put(action)
发起一个 action 到 Storecall(fn,...args)
调用 fn 函数并以 args 为参数,如果结果是一个 Promise,middleware 会暂停直到这个 Promise 被 resolve,resolve 后 Generator 会继续执行。 或者直到 Promise 被 reject 了,如果是这种情况,将在 Generator 中抛出一个错误。
Redux-saga 详细api文档
-
结语
我在工作时用的是 Redux-Thunk, Redux-Thunk 相对来说更容易实现和维护。但是对于复杂的操作,尤其是面对复杂异步操作时,Redux-saga 更有优势。到此我们完成了一个 Redux-saga 的入门教程,Redux-saga 还有很多奇妙的地方,大家可以自行探索。