简化redux中的action和reducer

如何让action和reducer更简单,这就是本文所需要记录的。可直接跳到改进部分。

前言

最近做的项目中,也使用了redux。redux是基于纯函数的,为了保证其纯度,它的reducer的要求是S’ = f(S)的这种形式。但是在实际项目中,我们有很多网络请求,那么要求reducer的形式是S’ = await f(Async)(S)的形态。但是在reducer中这是不允许的(为了保证其纯度),所以才会出现了各种各样的基于redux异步库,redux-thunk,redux-promise,redux-promise-middleware,redux-saga……

如何设计action

本文不是探讨几种异步方案的差别的,这里讲到了几种方案。项目中我使用的是redux以及异步库redux-thunk。我将说说我是如何设计使redux这种异步使用起来更简单一点。

乐观更新

首先,我抛弃了乐观更新。通常一个action—-reducer的流程是这样的。

//action types
const GET_DATA = 'GET_DATA';
const GET_DATA_SUCCESS = 'GET_DATA_SUCCESS';
const GET_DATA_FAILED = 'GET_DATA_FAILED';

//action creator
const getDataAction = (someData) => (dispatch, getState) => {
  dispatch({ type: GET_DATA, data: someData});

  fetch() // 我使用的是fetch,并对fetch做了封装
  .then(res => res.json()) // 以json数据为例
  .then(jsonData => {
    dispatch({ type: GET_DATA_SUCCESS })
  })
  .catch(err => {
    dispatch({ type: GET_DATA_FAILED })
  })
}

//reducer
const dataReducer = (state, action) => {
    switch(action.type) {
    case GET_DATA : 
        return oldState;
    case GET_DATA_SUCCESS : 
        return successState;
    case GET_DATA_FAILED : 
        return errorState;
    }
}

这仅仅是最简单的reducer了,我还没有写任何业务逻辑部分的代码。可以看到这种写法还是很麻烦(三个常量,三个action,三个case)。第一个dispatch({ type: GET_DATA, data: someData});就是为了乐观更新。假如我们现在有这样一个业务需求—需要添加一个商品到购物车。

  • 点击添加到购物车按钮

  • dispatch一个action,action中携带着商品相关数据

  • 在action的处理中,需要发送异步请求更新数据库的购物车。这时候,如果我们在发起异步请求前先做一个这样的action。dispatch({ type: UPDATE_SHOPCART, data: someProduct});,这时候reducer收到了这个action,就会更新store中关于购物车的部分。然后在异步请求成功或者失败后分别还会dispatch一个action。如果成功了,代表我们之前更新store中关于购物车的部分是无误的。如果失败了,我们再从store的购物车中去掉关于这个商品的信息即可。这就是乐观更新。
    这就和我们发微信类似,在我们点击发送的那一瞬间,消息已经进入对话框了(乐观更新,总是假设它是成功的),然后如果由于网络问题发送失败了就会额外做一个标志。

这个项目中,我没有使用乐观更新,我觉得太麻烦。而且对用户来说,看的是添加成功了,然后又来一个添加失败的通知,想必体验也不会好。

除了乐观更新

除了乐观更新到需要的一个action,还有两个action。分别表示成功和失败。一般而言会这样:

fetch() // 我使用的是fetch,并对fetch做了封装
  .then(res => res.json()) // 以json数据为例
  .then(jsonData => {
    dispatch({ 
      type: UPDATE_SHOPCART_SUCCESS,
      payload: {
        data: jsonData
      } 
    })
  })
  .catch(err => {
    dispatch({ 
      type: UPDATE_SHOPCART_FAILED,
      payload: { err }
    })
  })

嗯,这种情况还是需要定义两个action(两个额外的常量,两个case)。我觉得还是很麻烦。我于是就想着还可以怎么改进,让写法更简单?让我更加少写点代码?

改进① – 两种情况我只需要一个action

还以更新购物车为例:

// action
const updateShopcart = (someProduct) => (dispatch, getState) {
  fetch(SOME_API, SOME_OPTIONS) 
  .then(res => res.json()) // 假设是json数据
  .then(jsonData => {
    dispatch(jsonData => {
      dispatch({ // 实际情况中该plain object可封装成一个函数
        type: UPDATE_SHOPCART,
        payload: {
          returnedData: jsonData 
          data: someProduct 
        }
      });
    })
  }) 
  .catch(err => {
    dispatch({
      type: UPDATE_SHOPCART,
      payload: {
        err,
      }
    });
  })
}

我只是将action合二为一了而已,并携带不同的字段。这样的话在recuder中处理就稍显复杂。

// reducer
const shopcart = (state, action) => {
  switch(action.type) {
    case 'UPDATE_SHOPCART':
      if(Object.prototype.toString.call(action.payload.err) === '[object Error]') { // 我喜欢这样去判断
        return errorState; // 这里根据错误返回实际情况,或给出一个错误字段等
      } 
      if(action.payload.returnedData.code === 1) { // 这是因为后端返回的code为1才表示成功
        return {
          ...state,
          shopcart: [action.payload.data, ...shopcart] // 更新购物车,就不需要再去服务器请求数据了
        }
      }
  }
}

可以看到,这只是reducer对一个数据的处理,对于每个异步请求都要这样处理,还是挺麻烦的(虽然已经简化了action)。经过一番思考,再次改进。

改进② – 使用中间件来处理错误

redux中,中间件在action和reducer之间。也就是说,所有的action都要经过中间件。如果在中间件中处理了这些错误,岂不是很美妙?关于中间件,我在[reudx源码分析中有提到]。(http://blog.csdn.net/real_bird/article/details/72872566)

const errorReporter = store => next => action => {
  if(Object.prototype.toString.call(action.payload.err) === '[object Error]') {
    switch(action.type) {
      // 根据不同的action处理不同的错误,为了不让业务处理逻辑代码注入到中间件,可根据不同的action.type返回不同的action
    }
    // 如果要统一处理错误,就不需要上面的switch了,可直接返回一个特别的action,比如
    return next({
      type: '@FAIL'
      payload: { 
        err: action.payload.err, 
        message: action.payload.returnedData.msg // // 后台返回的错误消息
      } 
    })
  } else { // 后台返回了数据的情况
    let statusCode = action.payload.returnedData.code;
    if(statusCode === 1) { // 协定为1表示成功
      return next(action); // 这样在reducer中只处理成功的情况
    } else {
      // ......
    }
  }
}

这样reducer就简单了。

// reducer
const shopcart = (state, action) => {
  switch(action.type) {
    case 'UPDATE_SHOPCART':   
      return {
        ...state,
        shopcart: [action.payload.data, ...shopcart] // 更新购物车,就不需要再去服务器请求数据了
      }
    case '@FAIL': 
      return {
        ...state,
        err: action.payload.err,
        message: action.payload.message,
      }
  }
}

这就是在这个项目中我想到了两点改进reducer的方法。不过为了再减少麻烦,我直接在中间件中处理错误了,比如给出通知消息什么的。让中间件显得不干净,但是少了很多代码。这不正是“美观”与“简单”之间的一次博弈吗。很多时候都是,无法兼顾。redux让数据流更清晰,但也让我们多写了很多代码。

最后

我觉得redux还是挺麻烦的。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值