在浅说Flux开发中,简单介绍了Flux及其开发方式。Flux能够说是一个框架,其有自己的 Dispatcher
接口供开发者;也能够说是一种数据流单向控制的架构设计,围绕单向数据流的核心,其定义了一套行为规范,以下图:html
Redux的设计就继承了Flux的架构,并将其完善,提供了多个API供开发者调用。借着react-redux,能够很好的与React结合,开发组件化程度极高的现代Web应用。本文是笔者近半年使用react+redux组合的一些总结,不当之处,敬请谅解。react
Action是数据从应用传递到 store/state 的载体,也是开启一次完成数据流的开始。git
以添加一个todo的Action为例:github
{ type:'add_todo', data:'我要去跑步' }
这样就定义了一个添加一条todo的Action,而后就能经过某个行为去触发这个Action,由这个Action携带的数据(data)去更新store(state/reducer):redux
store.dispatch({ type:'add_todo', data:'your data' })
type
是一个常量,Action必备一个字段,用于标识该Action的类型。在项目初期,这样定义Action也能愉快的撸码,可是随着项目的复杂度增长,这种方式会让代码显得冗余,由于若是有多个行为触发同一个Action,则这个Action要写屡次;同时,也会形成代码结构不清晰。于是,得更改建立Action的方式:segmentfault
const ADD_TODO = 'add_todo'; let addTodo = (data='default data') => { return { type: ADD_TODO, data: data } } //触发action store.dispatch(addTodo());
更改以后,代码清晰多了,若是有多个行为触发同一个Action,只要调用一下函数 addTodo
就行,并将Action要携带的数据传递给该函数。相似 addTodo
这样的函数,称之为 Action Creator。Action Creator 的惟一功能就是返回一个Action供 dispatch
进行调用。api
可是,这样的Action Creator 返回的Action 并非一个标准的Action。在Flux的架构中,一个Action要符合 FSA(Flux Standard Action) 规范,须要知足以下条件:promise
是一个纯文本对象架构
只具有 type
、payload
、error
和 meta
中的一个或者多个属性。type
字段不可缺省,其它字段可缺省app
若 Action 报错,error
字段不可缺省,切必须为 true
payload
是一个对象,用做Action携带数据的载体。因此,上述的写法能够更改成:
let addTodo = (data='default data') => { return { type: ADD_TODO, payload: { data } } }
在 redux 全家桶中,能够利用 redux-actions 来建立符合 FSA 规范的Action:
import {creatAction} from 'redux-actions'; let addTodo = creatAction(ADD_TODO) //same as let addTodo = creatAction(ADD_TODO,data=>data)
能够采用以下一个简单的方式检验一个Action是否符合FSA标准:
let isFSA = Object.keys(action).every((item)=>{ return ['payload','type','error','meta'].indexOf(item) > -1 })
在我看来,Redux提升了两个很是重要的功能,一是 Reducer 拆分,二是中间件。Reducer 拆分可使组件获取其最小属性(state),而不须要整个Store。中间件则能够在 Action Creator 返回最终可供 dispatch 调用的 action 以前处理各类事情,如异步API调用、日志记录等,是扩展 Redux 功能的一种推荐方式。
Redux 提供了 applyMiddleware(...middlewares)
来将中间件应用到 createStore。applyMiddleware 会返回一个函数,该函数接收原来的 creatStore 做为参数,返回一个应用了 middlewares 的加强后的 creatStore。
export default function applyMiddleware(...middlewares) { return (createStore) => (reducer, preloadedState, enhancer) => { //接收createStore参数 var store = createStore(reducer, preloadedState, enhancer) var dispatch = store.dispatch var chain = [] //传递给中间件的参数 var middlewareAPI = { getState: store.getState, dispatch: (action) => dispatch(action) } //注册中间件调用链 chain = middlewares.map(middleware => middleware(middlewareAPI)) dispatch = compose(...chain)(store.dispatch) //返回经middlewares加强后的createStore return { ...store, dispatch } } }
建立 store 的方式也会因是否使用中间件而略有区别。未应用中间价以前,建立 store 的方式以下:
import {createStore} from 'redux'; import reducers from './reducers/index'; export let store = createStore(reducers);
应用中间价以后,建立 store 的方式以下:
import {createStore,applyMiddleware} from 'redux'; import reducers from './reducers/index'; let createStoreWithMiddleware = applyMiddleware(...middleware)(createStore); export let store = createStoreWithMiddleware(reducers);
那么怎么自定义一个中间件呢?
根据 redux 文档,中间件的签名以下:
({ getState, dispatch }) => next => action
根据上文的 applyMiddleware
源码,每一个中间件接收 getState & dispatch 做为参数,并返回一个函数,该函数会被传入下一个中间件的 dispatch 方法,并返回一个接收 action 的新函数。
以一个打印 dispatch action 先后的 state 为例,建立一个中间件示例:
export default function({getState,dispatch}) { return (next) => (action) => { console.log('pre state', getState()); // 调用 middleware 链中下一个 middleware 的 dispatch。 next(action); console.log('after dispatch', getState()); } }
在建立 store 的文件中调用该中间件:
import {createStore,applyMiddleware} from 'redux'; import reducers from './reducers/index'; import log from '../lib/log'; //export let store = createStore(reducers); //应用中间件log let createStoreWithLog = applyMiddleware(log)(createStore); export let store = createStoreWithLog(reducers);
能够在控制台看到输出:
能够对 store 应用多个中间件:
import log from '../lib/log'; import log2 from '../lib/log2'; let createStoreWithLog = applyMiddleware(log,log2)(createStore); export let store = createStoreWithLog(reducers);
log2 也是一个简单的输出:
export default function({getState,dispatch}) { return (next) => (action) => { console.log('我是第二个中间件1'); next(action); console.log('我是第二个中间件2'); } }
看控制台的输出:
应用多个中间件时,中间件调用链中任何一个缺乏 next(action)
的调用,都会致使 action 执行失败
Redux 自己不处理异步行为,须要依赖中间件。结合 redux-actions 使用,Redux 有两个推荐的异步中间件:
两个中间件的源码都是很是简单的,redux-thunk 的源码以下:
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;
从源码可知,action creator 须要返回一个函数给 redux-thunk 进行调用,示例以下:
export let addTodoWithThunk = (val) => async (dispatch, getState)=>{ //请求以前的一些处理 let value = await Promise.resolve(val + ' thunk'); dispatch({ type:CONSTANT.ADD_TO_DO_THUNK, payload:{ value } }); };
效果以下:
这里之因此不用 createAction,如前文所说,由于 createAction 会返回一个 FSA 规范的 action,该 action 会是一个对象,而不是一个 function:
{ type: "add_to_do_thunk", payload: function(){} }
若是要使用 createAction,则要自定义一个异步中间件。
export let addTodoWithCustom = createAction(CONSTANT.ADD_TO_DO_CUSTOM, (val) => async (dispatch, getState)=>{ let value = await Promise.resolve(val + ' custom'); return { value }; });
在通过中间件处理时,先判断 action.payload 是不是一个函数,是则执行函数,不然交给 next 处理:
if(typeof action.payload === 'function'){ let res = action.payload(dispatch, getState); } else { next(action); }
而 async 函数返回一个 Promise,于是须要做进一步处理:
res.then( (result) => { dispatch({...action, payload: result}); }, (error) => { dispatch({...action, payload: error, error: true}); } );
这样就自定义了一个异步中间件,效果以下:
固然,咱们能够对函数执行后的结果是不是Promise做一个判断:
function isPromise (val) { return val && typeof val.then === 'function'; } //对执行结果是不是Promise if (isPromise(res)){ //处理 } else { dispatch({...action, payload: res}); }
那么,怎么利用 redux-promise 呢?redux-promise 是能处理符合 FSA 规范的 action 的,其对异步处理的关键源码以下:
action.payload.then( result => dispatch({ ...action, payload: result }), error => { dispatch({ ...action, payload: error, error: true }); return Promise.reject(error); } )
于是,返回的 payload 再也不是一个函数,而是一个 Promise。而 async 函数执行后就是返回一个 Promise,因此,让上文定义的 async 函数自执行一次就能够:
export let addTodoWithPromise = createAction(CONSTANT.ADD_TO_DO_PROMISE, (val) => (async (dispatch, getState)=>{ let value = await Promise.resolve(val + ' promise'); return { value }; })() );
结果以下图:
示例源码:redux-demo