博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
优雅地减少redux请求样板代码
阅读量:6591 次
发布时间:2019-06-24

本文共 8533 字,大约阅读时间需要 28 分钟。

在日常开发过程中我们采用react+redux方案进行开发,往往会遇到redux样板代码过多的问题,在不断的抽离过程中,顺手封装了一个。在此进行详细的问题和解决思路。最终代码和示例可以再项目中查看并使用,欢迎使用、建议并star~

抛出问题

使用Redux进行开发时,遇到请求,我们往往需要很复杂的过程,并且这个过程是重复的。我们往往会把一个请求拆分成三个阶段,对应到三个Action Type中去,并且配合中间件,将一个异步action进行拆分,分别对应请求的三个阶段。如下所示:

// 请求的三个状态,开始请求,请求成功,请求失败export const START_FETCH = 'START_FETCH'export const FETCH_SUCCESS = 'FETCH_SUCCESS'export const FETCH_FAILED = 'FETCH_FAILED'const startFetch = () => ({  type: START_FETCH})const fetchSuccess = payload => ({  type: FETCH_SUCCESS,  payload})const fetchFailed = error => ({  type: FETCH_FAILED,  error})// 在请求的三个阶段中,dispatch不同的actionexport const fetchData = (params) => (dispatch) => {  // 开始请求  dispatch(startFetch())  return fetch(`/api/getData`)    .then(res => res.json())    .then(json => {      dispatch(fetchSuccess(json))    })    .catch(error => {      dispatch(fetchFailed(error))    })}复制代码

同时,我们需要在reducer中,添加三个action所对应的状态更改,来相应的对整个请求进行展示。例如:

  • 开始请求时进行loading, 需要loading字段
  • 请求成功时结束loading, 修改data
  • 请求失败时结束loading, 展示error

对应我们需要写以下内容:

const initialData = {  data: {},  loading: false,  error: null}const data = (state = initialData, action) => {  switch(action.type) {    case START_FETCH:      return {        ...state,        loading: true,        error: null      }    case FETCH_SUCCESS:      return {        ...state,        loading: false,        data: action.payload      }    case FETCH_FAILED:      return {        ...state,        loading: false,        error: action.error      }    default:      return state  }})复制代码

针对一个完整健壮的请求,我们往往需要把上述的代码全部写一遍。假设我们一个页面有N个请求接口,我们需要把这些近似相同的代码书写无数遍,显然是很麻烦又不太好的做法,那么我们***如何在保证代码流程和可读性的同时,来减少样板代码呢***

初步解决方案,使用函数把它封装起来

其实针对这种重复代码,我们第一个想到的就是把它封装成一个函数,将可变因素作为一个参数即可。 但是这个可能稍微复杂一点,因为针对这个函数,我们可能会进行几个不太相关的步骤,或者不能说是步骤,应该说是拿到不懂的我们想要的内容:

  1. 获取三个状态的action
  2. 在请求过程中,分别对三个action进行处理,并且可灵活配置请求参数,请求结果,错误处理等
  3. 自定义initialState,并且在reducer自动对应三个action状态,更新state

由于这个不是我们最终的方案,我直接将代码放出来,阐明我们基本的思路:

import update from "immutability-helper";// 根据actions来返回目的reducer, 此reducer会自动对单个过程更新state// 并且可以增加自定义的修改const reducerCreator = actions => (initState, otherActions) => {  const resultInitState = Object.assign({}, initState, {    isFetching: true,    isError: false,    ErrMsg: ""  });  const { START_ACTION, SUCCESS_ACTION, FAILED_ACTION } = actions;  return (state = resultInitState, action) => {    let ret;    switch (action.type) {      case START_ACTION:        ret = update(state, {          isFetching: {            $set: true          },          isError: {            $set: false          },          ErrMsg: {            $set: ""          }        });        break;      case SUCCESS_ACTION:        ret = update(state, {          isFetching: {            $set: false          }        });        break;      case FAILED_ACTION:        ret = update(state, {          isFetching: {            $set: false          },          isError: {            $set: true          }        });        break;      default:        ret = state;    }    return otherActions(ret, action);  };};// 1.创建三个action// 2.执行请求函数, 在请求中我们可以任意的格式化参数等// 3.请求过程中执行三个action// 4.根据三个action返回我们的reducerexport default (action, fn, handleResponse, handleError) => {  const START_ACTION = Symbol(`${action}_START`);  const SUCCESS_ACTION = Symbol(`${action}_SUCCESS`);  const FAILED_ACTION = Symbol(`${action}_FAILED`);  const start = payload => ({    type: START_ACTION,    payload  });  const success = payload => ({    type: SUCCESS_ACTION,    payload  });  const failed = payload => ({    type: FAILED_ACTION,    payload  });  return {    actions: {      [`${action}_START`]: START_ACTION,      [`${action}_SUCCESS`]: SUCCESS_ACTION,      [`${action}_FAILED`]: FAILED_ACTION    },    method: (...args) => (dispatch, getState) => {      dispatch(start());      return fn(...args, getState)        .then(r => r.json())        .then(json => {          if (json.response_code === 0) {            const ret = handleResponse              ? handleResponse(json, dispatch, getState)              : json;            dispatch(success(ret));          } else {            dispatch(failed(json));          }        })        .catch(err => {          const ret = handleError ? handleError(err) : err;          dispatch(failed(err));        });    },    reducerCreator: reducerCreator({      START_ACTION,      SUCCESS_ACTION,      FAILED_ACTION    })  };};复制代码

通过这个工具函数,我们可以极大的简化整个流程,针对一个请求,我们可以通过以下方式进行:

const getDataFn = params => {  return fetch("/api/getData", {    method: "POST",    headers: {      "Content-type": "application/json; charset=UTF-8"    },    body: JSON.stringify(params)  });};export const {  // 三个action  actions: getDataActions,  // 创建reducer  reducerCreator: getDataReducerCreator,  // 请求,触发所有的过程  method: getData} = reduxCreator("GET_DATA", getDataFn, res => res.data);复制代码

在reducer中,我们可以直接使用reducerCreator创建reducer, 并且可以添加额外的内容

const initialData = {  list: []}// 最终的reducer,包含请求和错误状态,且根据请求自动更新const threatList = threatListReducerCreator(initialData, (state, action) => {  switch (action.type) {    case getDataActions.GET_DATA_SUCCESS:      return update(state, {        list: {          $set: action.payload.items        }      });    default:      return state;  }})复制代码

通过这种方式,我们极大的减少了整个过程的代码,并且可以在每个过程中灵活的加入我们想要的东西。 配合我封装的中的Box组件,很方便的实现 请求->loading->展现内容的过程。

但是,总是隐约觉得这个代码有些不舒服,不舒服在哪儿呢? 没错,虽然它很大程度的简化了代码,但是使用这个工具函数后,极大的***改变了整个redux代码的结构***, 整个函数使用过程及***语义化十分不明显,我们很难一眼看出来我们都做了什么***。 并且,不熟悉Api的人用起来会十分难受

因此,我们对以上代码进行改善,以达到我们最终的要求:优雅

引子

Redux借鉴Koa的中间件机制,也给我们提供了一个很好的middleware使用。具体的原理我们在此不进行赘述,我们来看下一个基础的middleware长什么样子:

const logMiddleware = store => next => action => {  console.log(action)  next(action)  console.log(action, 'finish')}复制代码

我们会看到,在一个middleware中,我们可以拿到store和action, 并且自动的执行下一个中间件或者action。 基本获取了我们所有需要的内容,我们可以直接在将请求过程中的固定代码,交给middleware来做!

使用redux-middleware简化流程

我们可以将分发action的过程在此自动进行,相信很多人都会这么做,我们只需要定义我们的特殊action的格式,并且针对此action进行特殊处理即可。比如我们定义我们的请求action为这样:

{  url: '/api/getData',  params,  types: [ START_ACTION, SUCCESS_ACTION, FAILED_ACTION ],  handleResult,  handleError,}复制代码

在middleware中,我们可以进行以下处理:

const fetchMiddleware = store => next => action => {  // 普通action直接执行  if (!action.url || !Array.isArray(action.types)) {    return next(action)  }  // 处理我们的request action  const {    handleResult = val => val,    handleError = error => error,    types, url, params  } = action  const [ START, SUCCESS, FAILED ] = types  next({    type: START,    loading: true,    ...action  })  return fetchMethod(url, params)    .then(handleResponse)    .then(ret => {      next({        type: SUCCESS,        loading: false,        payload: handleResult(ret)      })      return handleResult(ret)    })    .catch(error => {      next({        type: FAILED,        loading: false,        error: handleError(error)      })    })}复制代码

同时,我们提供actionCreator, reducerCreator来创建对应的action, 和reducer。保证流程和结构不变的情况下,简化代码。

最终版本

  1. apply middleware
import createFetchMiddleware from 'redux-data-fetch-middleware'import { applyMiddleware } from 'redux'import thunk from 'redux-thunk'// 设置公用的请求函数const fetchMethods = (url, params) => fetch(url, {    method: "post",    headers: {      "Content-type": "application/json; charset=UTF-8"    },    body: JSON.stringify(params)  })// 设置共用的处理函数,如进行统一的错误处理等const handleResponse = res => res.json()const reduxFetch = createFetchMiddleware(fetchMethods, handleResponse)const middlewares = [thunk, reduxFetch]applyMiddleware(...middlewares)复制代码
  1. actions
import { actionCreator } from 'redux-data-fetch-middleware'// 创建三个actionexport const actionTypes = actionCreator('GET_USER_LIST')export const getUserList = params => ({  url: '/api/userList',  params: params,  types: actionTypes,  // handle result  handleResult: res => res.data.list,  // handle error  handleError: ...})// 可以直接dispatch,自动执行整个过程dispatch(getUserList({ page: 1 }))复制代码
  1. reducer
import { combineReducers } from 'redux'import { reducerCreator } from 'redux-data-fetch-middleware'import { actionTypes } from './action'const [ GET, GET_SUCCESS, GET_FAILED ] = actionTypes// userList会自动变成 {
// list: [],// loading: false,// error: null// }// 并且当GET, GET_SUCCESS and GET_FAILED改变时,会自动改变loading,error的值const fetchedUserList = reducerCreator(actionTypes)const initialUserList = { list: []}const userList = (state = initialUserList, action => { switch(action.type) { case GET_SUCCESS: return { ...state, action.payload } }})export default combineReducers({ userList: fetchedUserList(userList)})复制代码

总结

从开始的问题抛出到解决思路到不断完善的过程,是解决问题的标准流程。通过这次封装,我们很好的解决了日常开发过程中Redux请求代码冗余的问题,并且也充分的了解了redux-middleware的机制。欢迎指正且star~

转载地址:http://ieuio.baihongyu.com/

你可能感兴趣的文章
如何写出兼容大部分浏览器的CSS 代码
查看>>
第二阶段冲刺第八天,6月7日。
查看>>
java的左移位(<<)和右移位(>>)和无符号右移(>>>)
查看>>
struts2 action 返回类型分析
查看>>
【原创】FPGA开发手记(三) PS/2键盘
查看>>
linux统计多个文件大小总和
查看>>
java基础-Eclipse开发工具介绍
查看>>
JS常见的字符串操作
查看>>
洛谷P1069 细胞分裂 数学
查看>>
JAVA中的编码分析
查看>>
查看源代码Source not found及在eclipse中配置jdk的src.zip源代码
查看>>
document.all用法
查看>>
uniGUI试用笔记(二)
查看>>
HOG特征-理解篇
查看>>
Microsoft.AlphaImageLoader滤镜解说
查看>>
extjs_02_grid(显示本地数据,显示跨域数据)
查看>>
超过响应缓冲区限制
查看>>
ubuntu 下安装 matplotlib
查看>>
webservice的几个简单概念
查看>>
underscore 1.7.0 api
查看>>