您的位置:首页 > 其它

简化redux中的action和reducer

2017-08-12 11:32 218 查看
如何让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还是挺麻烦的。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  redux