图解Redux数据流(二)

图解Redux数据流(二)

Nov 28, 2016

上一篇文章回答了前两个问题,而这一篇则回答最后一个问题。

Redux如何使用?

前一篇文章描述了Redux的基本要素,并且梳理了一下Redux的数据流动图

Alt text

首先,我们的View上体现出的任何操作或者事件都会调用Dispatch方法触发Action
Action 会告知了type和data
然后,Store自动调用Reducer,并且传入两个参数:当前 State和收到的Action。 Reducer会返回新的State 。并且这个过程是纯函数式的,返回的结果由入参决定,不会随着外界的变化而改变。

知道概念了,那么我们从代码层面如何搭建出Redux数据流的架构呢?

最基本的Redux数据流

 

import { createStore } from 'redux';

const initialState = {

items: []

};

const reducer = (state = initialState, action = {}) => {

const { type } = action;

switch (type) {

case "ADD_ITEM":

return Object.assign({}, state, {

items: state.items.concat(action.item)

});

break;

case "GET_ITEM_LIST":

return Object.assign({}, state, {

items: action.items

});

break;

}

}

const store = createStore(reducer)

store.subscribe(() => {

console.log(store.getState())

})

store.dispatch({type: 'GET_ITEM_LIST','items':[{id:1,name:'test'}]})

store.dispatch({type: 'ADD_ITEM','item':[{id:2,name:'test2'}]})

这个代码段是最简单的Redux数据流例子,把创建Store、ActionCreator、Reducer全放在了一个文件中,在实际项目中,这当然不可取。
其实每个Redux的实体概念都可以拆解成一个单独的文件去管理。
在项目中为了项目的规范化,我们可以对所有的action_type常量进行统一的管理,放到单独的文件中

 

const GET_ITEM_LIST = 'GET_ITEM_LIST'

const ADD_ITEM = 'ADD_ITEM'

当然,也许最需要进行拆分是Reducer,在有些规模的实际项目中,state往往比较庞大。对于Reducer而言,我们最好根据相应的业务需求拆分出不同Reducer来管理。

 

import { createStore,combineReducers } from 'redux';

const itemReducer = (state = {}, action = {}) => {

const { type } = action;

switch (type) {

case "ADD_ITEM":

return {

...state,

items: state.items.concat(action.item)

}

break;

case "DELETE_ITEM":

return {

...state,

items: state.items.filter((item)=> {

return !(item.id === action.itemId);

})

}

break;

}

}

}

const ListReducer = (state = {items: []}, action) => {

const { type } = action;

switch (type) {

case "GET_ITEM_LIST":

return {

...state,

items: action.items

}

break;

case "CLEAR_ITEM_LIST":

return {

...state,

items: []

}

break;

}

}

}

const reducer = combineReducers({itemReducer,ListReducer})

可以看到,Redux其实本身也提供了combineReducers方法来帮助开发者合并Reducer,可以让每个Reducer互相独立。

Middleware中间件

Redux中,一切数据都是从一个状态到另一个状态,那么也许我们需要在这个状态间添加一些自己的方法或者功能呢?
这个时候就需要Middleware,Middleware发生在发送Action时,也就是调用store.dispatch()。
在Middleware中,我们通过调用next(action)函数就可以把Action传递给Reducer。
例如一个典型的简易版redux-logger模块,

 

import { createStore, applyMiddleware } from 'redux';

const initialState = {

items: []

};

const reducer = (state = initialState, action = {}) => {

const { type } = action;

switch (type) {

case "ADD_ITEM":

return {

...state,

items: state.items.concat(action.item)

}

break;

case "GET_ITEM_LIST":

return {

...state,

items: action.items

}

break;

}

}

const reduxlog = store => next => action => {

console.log(`prev state`, store.getState())

console.log(`action:`, action.type)

next(action)

console.log(`next state`,store.getState())

}

const store = createStore(reducer, initialState, applyMiddleware(reduxlog))

store.subscribe(() => {

console.log(store.getState())

})

store.dispatch({type: 'GET_ITEM_LIST','items':[{id:1,name:'test'}]})

store.dispatch({type: 'ADD_ITEM','item':[{id:2,name:'test2'}]})

Alt text

中间件处理了改变前和改变后的状态,写法也非常容易理解,如果有业务需要对状态集中处理,通过中间件的方式也不失为一种选择。
文章头部的那张图,如果加上中间件,就是这样:

Alt text

中间件的顺序

 

const store = createStore(

reducer,

applyMiddleware(thunk, promise, logger)

);

中间件的调用顺序其实还是有一定讲究,这就要从Middleware本身的设计思想来说明。
Redux深受函数式编程的影响,中间件的设计也不例外,

middleware 的设计有点特殊,是一个层层包裹的匿名函数,这其实是函数式编程中的柯里化 curry,一种使用匿名单参数函数来实现多参数函数的方法。applyMiddleware 会对 logger 这个 middleware 进行层层调用,动态地对 store 和 next 参数赋值。

Redux的applyMiddleware会将所有的中间件组合串联,

compose 将 chain 中的所有匿名函数,[f1, f2, … , fx, …, fn],组装成一个新的函数,即新的 dispatch,当新 dispatch 执行时,[f1, f2, … , fx, …, fn],从左到右依次执行( 所以顺序很重要)

具体中间件的实现思路不详细展开,知乎上有一篇专栏分析的很到位,有兴趣可以看一下 链接

上面的例子里如果logger中间件不放置在最后面,输出结果会不正确。

异步问题

刚刚我们看了那么多示例代码?但是至此,Redux一直没有解决异步的问题。试想,如果我在页面输入一段内容,然后触发了一个动作,此时需要向服务端请求数据并将返回的数据展示出来。这是一个很常见的需求,但是涉及到异步请求,刚刚的示例中的方法已经不再适用了。那么Redux是如何解决异步问题的呢?

没错,还是Middleware,Middleware只关注dispatch函数的传递,至于在传递的过程中干了什么中间件并不关心。
这里不得不提redux-thunk这个中间件

redux-thunk的基本思想就是通过函数来封装异步请求,也就是说在actionCreater中返回一个函数,在这个函数中进行异步调用。

 

function createThunkMiddleware(extraArgument) {

return ({ dispatch, getState }) => next => action => {

if (typeof action === 'function') {

return action(dispatch, getState, extraArgument);

}

return next(action);

};

}

const thunk = createThunkMiddleware();

thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

其实打开redux-thunk的源码看一下,讲真代码量就十几行,那么这里做了什么处理呢?
其实关键的就一句代码

 

if (typeof action === 'function') {

return action(dispatch, getState, extraArgument);

}

我们知道Action正常其实返回的是一个js对象,如果没有经过Middleware的处理,是不符合Redux逻辑,会抛出异常的,所以redux-thunk只做了一件事件那就是让Action能够兼容 return 函数,redux-thunk内部再去消化掉这个函数。

 

function actionCreate() {

return function (dispatch, getState) {

api.fetch(data).then(function (json) {

dispatch(json);

})

}

}

总结

以上是最近一段时间通过在业务不断实践,然后学习和思考的总结。

在这期间发现,很多东西需要事先去积累,要拓宽自己的视野,如果自己不知道某种设计思路并且没有相关经验时,很难想到点上。

技术与业务是相辅相成的,技术能够很好的帮助自己拓宽视野,能设计更好的项目架构;而业务能够让技术得以实践,并且发现技术上可能还存在的问题,从而积累经验。

这个思路我通过这段时间的学习感悟到的,今后的学习也会沿着这个思路走下去。

PREVNEXT

© 2016 - 2017 alisec-ued, powered by Hexo

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值