簡介
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 (
<div>
<button onClick={onFetchUser}>請求數據</button>
<div>{JSON.stringify(userInfo)}</div>
</div>
)
}
擴展
有的時候,我們需要在某些異步操作結束后(比如接口請求完成后)做某些操作
使用 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 (
<div>
<button onClick={onFetchUser}>請求數據</button>
<div>{JSON.stringify(userInfo)}</div>
</div>
)
}