简介
Redux 相关知识参考:Redux基本使用
Redux 中的 reducer 只能处理同步
如果需要用 Redux 处理异步请求,可以使用异步相关的插件。
一般有两个步骤:1、异步请求接口;2、将请求结果存到 store 中
比较常用的有:redux-thunk
、redux-saga
redux-thunk 使用
安装:
npm i redux-thunk --save
使用
applyMiddleware
添加中间件使用
compose
合并中间件和 redux-devtools (只有这个中间件的话,就没必要用这个方法了)修改
src/store/index.js
文件
import { createStore, applyMiddleware, compose } from 'redux'
import thunk from 'redux-thunk'
import rootReducer from './reducers'
export default createStore(rootReducer, compose(
applyMiddleware(thunk),
window.devToolsExtension ? window.devToolsExtension() : f=>f
))
- 修改
src/store/action.js
,添加异步方法
// ...
export const testCountAddAsync = num => {
return dispatch => {
setTimeout(() => {
dispatch(testCountAdd(num))
}, 2000)
}
}
// ...
- 在组件中使用
@connect(
({ test }) => ({ count: test.count}),
{ testCountAddAsync }
)
class Demo extends React.Component {
// ...
}
export default Demo
redux-saga
使用的是 ES6 的 generator 语法。该语法知识参考:一文掌握生成器 Generator,利用 Generator 实现异步编程
备注:
下面示例中的代码,使用 redux-actions 管理 actions。
相关使用参考:使用redux-actions优化actions管理
安装:
npm i redux-saga --save
修改
src/store/actions.js
文件,添加 action
import { createActions } from 'redux-actions'
// 建议区分开 action 的用途,方便后期维护
// ON_XXX 开头的给 saga 用
// 其他的,比如 SET_XXX、ClEAR_XXX 等给 reducer 用
export default createActions({
// ...
DEMO: {
ON_USER_INFO: undefined,
SET_USER_INFO: undefined,
}
})
- 在
src/store/reducer/
目录下新建demo.js
文件
import { handleActions } from 'redux-actions'
import Actions from '../actions'
const INITIAL_STATE = {
userInfo: null,
}
export default handleActions({
[Actions.demo.setUserInfo](state, { payload }) {
return {
...state,
userInfo: payload
}
},
}, INITIAL_STATE)
- 修改
src/store/reducer/index.js
文件,添加 demo
import { combineReducers } from 'redux'
// ...
import demo from './demo'
export default combineReducers({
// ...
demo,
})
在
src/store/
目录下添加 saga/ 目录在
src/store/saga/
目录下添加demo.js
文件
import { all, takeLatest, put } from 'redux-saga/effects'
import Actions from '../actions'
// 延时,用来模拟请求
export const delay = (time) => {
return new Promise((resolve, reject) => {
setTimeout(() => {
resolve();
}, time);
});
};
// 模拟请求
const request = async (url, options) => {
console.log(url, options)
await delay(1000)
return {
code: 0,
data: {
name: 'wmm66',
age: 18,
}
}
}
export function* fetchUserInfo({ payload }) {
const { id } = payload || {}
const res = yield request('user/info', { id })
if (res && res.code === 0) {
yield put(Actions.demo.setUserInfo(res.data))
}
}
export default all([
takeLatest(Actions.demo.onUserInfo, fetchUserInfo),
])
- 在
src/store/saga/
目录下添加index.js
文件
import { all } from 'redux-saga/effects'
import demoSaga from './demo'
export default function* rootSage() {
yield all([
demoSaga
])
}
- 修改
src/store/index.js
文件
import { createStore, applyMiddleware, compose } from 'redux'
import createSagaMiddleware from 'redux-saga'
import rootReducer from './reducer'
import rootSaga from './saga'
const sagaMiddleware = createSagaMiddleware()
const composeEnhancers = typeof window === 'object' && window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__
? window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__({
// Specify extension’s options like name, actionsBlacklist, actionsCreators, serialize...
}) : compose;
const middlewares = [sagaMiddleware];
// if (process.env.NODE_ENV === 'development') {
// middlewares.push(require('redux-logger').createLogger());
// }
const enhancer = composeEnhancers(applyMiddleware(...middlewares))
const store = createStore(rootReducer, enhancer);
sagaMiddleware.run(rootSaga);
export default store
export { default as Actions } from './actions'
- 页面中使用
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import Actions from '@/store/actions'
export default function Test(props) {
const { userInfo } = useSelector(({ demo }) => demo)
const dispatch = useDispatch()
const onFetchUser = () => {
dispatch(Actions.demo.onUserInfo({ id: 123 }))
}
return (
{JSON.stringify(userInfo)}
)
}
扩展
有的时候,我们需要在某些异步操作结束后(比如接口请求完成后)做某些操作
使用 Promise 的时候,我们可以使用 then、async/await 等很方便的做到
generator 语法可以使用 callback 的方式实现
- 基本代码如下:
// src/store/saga/demo.js文件
export function* fetchUserInfo({ payload }) {
const { id, callback } = payload || {}
const res = yield request('user/info', { id })
if (res && res.code === 0) {
yield put(Actions.demo.setUserInfo(res.data))
callback && callback(res.data)
}
}
// 使用
const onFetchUser = () => {
dispatch(Actions.demo.onUserInfo({ id: 123, callback: (userInfo) => {
console.log(userInfo)
} }))
}
- 我们可以将其 Promise 化,方便使用
const promisifySaga = () => {
return new Promise((resolve, reject) => {
dispatch(Actions.demo.onUserInfo({ id: 123, callback: (userInfo) => {
resolve(userInfo)
} }))
})
}
const onFetchUser = async () => {
const userInfo = await promisifySaga()
console.log(userInfo)
}
- promisifySaga 应该是一个比较通用的方法,我们通过传参来实现
function promisifySaga({dispatch, action, params = {}}) {
return new Promise((resolve, reject) => {
if (!dispatch || !action || typeof action !== 'function') {
reject('参数错误')
return
}
dispatch(action({ ...params, callback: (res) => {
resolve(res)
} }))
})
}
const onFetchUser = async () => {
const userInfo = await promisifySaga({
dispatch,
action: Actions.demo.onUserInfo,
params: { id: 123 }
})
console.log(userInfo)
}
- promisifySaga 的意思是将 saga 给 promise 化,我们希望这么调用
promisifySaga({dispatch, action})({ id: 123 })
function promisifySaga({dispatch, action}) {
return (params = {}) => {
return new Promise((resolve, reject) => {
if (!dispatch || !action || typeof action !== 'function') {
reject('参数错误')
return
}
dispatch(action({ ...params, callback: (res) => {
resolve(res)
} }))
})
}
}
const onFetchUser = async () => {
const userInfo = await promisifySaga({
dispatch,
action: Actions.demo.onUserInfo
})({ id: 123 })
console.log(userInfo)
}
- 最终代码如下
// src/store/saga/demo.js
export function* fetchUserInfo({ payload }) {
const {
id,
callback = i => i,
} = payload || {}
// 这里推荐捕捉错误,往后抛
try {
const res = yield request('user/info', { id })
if (res && res.code === 0) {
yield put(Actions.demo.setUserInfo(res.data))
callback({ status: 'success', data: res.data })
} else {
callback({ status: 'error', reason: res.msg })
}
} catch(err) {
callback({ status: 'error', reason: err })
}
}
// src/lib/utils.js
import store from '@/store'
/**
* 是通过回调函数实现的
* 如果想在异步执行后执行某些操作,对应的saga必须设置callback
* 成功:callback({ status: 'success', data: res.data })
* 失败:callback({ status: 'error', reason: err })
*/
export const promisifySaga = action => {
return (params = {}) => {
return new Promise((resolve, reject) => {
if (!action || typeof action !== 'function') {
reject('参数错误')
return
}
store.dispatch(action({
...params,
callback: ({ status, data, reason }) => {
status === 'success'
? resolve(data)
: reject(reason)
},
}))
})
}
}
// 代码中使用
import React from 'react'
import { useSelector, useDispatch } from 'react-redux'
import Actions from '@/store/actions'
import { promisifySaga } from '@/lib/utils'
export default function Test(props) {
const { userInfo } = useSelector(({ demo }) => demo)
const dispatch = useDispatch()
const onFetchUser = () => {
const userInfo = await promisifySaga(Actions.demo.onUserInfo)({ id: 123 })
console.log(userInfo)
}
return (
{JSON.stringify(userInfo)}
)
}