简化redux中的action和reducer
2017-08-12 11:32
218 查看
如何让action和reducer更简单,这就是本文所需要记录的。可直接跳到改进部分。
这仅仅是最简单的reducer了,我还没有写任何业务逻辑部分的代码。可以看到这种写法还是很麻烦(三个常量,三个action,三个case)。第一个
点击添加到购物车按钮
dispatch一个action,action中携带着商品相关数据
在action的处理中,需要发送异步请求更新数据库的购物车。这时候,如果我们在发起异步请求前先做一个这样的action。
这就和我们发微信类似,在我们点击发送的那一瞬间,消息已经进入对话框了(乐观更新,总是假设它是成功的),然后如果由于网络问题发送失败了就会额外做一个标志。
这个项目中,我没有使用乐观更新,我觉得太麻烦。而且对用户来说,看的是添加成功了,然后又来一个添加失败的通知,想必体验也不会好。
嗯,这种情况还是需要定义两个action(两个额外的常量,两个case)。我觉得还是很麻烦。我于是就想着还可以怎么改进,让写法更简单?让我更加少写点代码?
我只是将action合二为一了而已,并携带不同的字段。这样的话在recuder中处理就稍显复杂。
可以看到,这只是reducer对一个数据的处理,对于每个异步请求都要这样处理,还是挺麻烦的(虽然已经简化了action)。经过一番思考,再次改进。
这样reducer就简单了。
这就是在这个项目中我想到了两点改进reducer的方法。不过为了再减少麻烦,我直接在中间件中处理错误了,比如给出通知消息什么的。让中间件显得不干净,但是少了很多代码。这不正是“美观”与“简单”之间的一次博弈吗。很多时候都是,无法兼顾。redux让数据流更清晰,但也让我们多写了很多代码。
前言
最近做的项目中,也使用了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】Action Reducer Store
- Redux系列01:从一个简单例子了解action、store、reducer
- Redux系列01:从一个简单例子了解action、store、reducer
- Redux系列01+核心概念 工作流程 安装 Action和Action创建函数 Reducer Store 数据流
- Redux中的bindActionCreators
- 创建自己的ActionContext对象简化开发
- [Redux] Extracting Action Creators
- 简化一下struts1的Action---把苍蝇包起来咽:(
- 【Spring】【笔记】《Spring In Action》第3章 简化XML配置
- Redux源码分析之bindActionCreators
- Reducer 最佳实践,Redux 开发最重要的部分
- 一种简化 Redux 的思路
- Redux:从action到saga
- Redux总结2:action
- Redux中的reducer到底是什么,以及它为什么叫reducer?
- Action Reducer Store
- [Redux] Avoid action type naming conflicts
- redux的合并多个reducer
- SpringMVC MultiActionController的使用-最简化教程
- 使用ControllerActionInvoker简化MVC单Form多按钮的提交