使用Redux管理异步数据

简介

Redux 相关知识参考:Redux基本使用

Redux 中的 reducer 只能处理同步

如果需要用 Redux 处理异步请求,可以使用异步相关的插件。

一般有两个步骤:1、异步请求接口;2、将请求结果存到 store 中

比较常用的有:redux-thunkredux-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)}
) }

你可能感兴趣的:(使用Redux管理异步数据)