关于Redux的一些总结(一):Action & 中间件 & 异步
在浅说Flux开发中,简单介绍了Flux及其开发方式。Flux可以说是一个框架,其有本身的 Redux的设计就继承了Flux的架构,并将其完善,提供了多个API供开发者调用。借着react-redux,可以很好的与React结合,开发组件化程度极高的现代Web应用。本文是笔者近半年使用react+redux组合的一些总结,不当之处,敬请谅解。 ActionAction是数据从应用传递到 store/state 的载体,也是开启一次完成数据流的开始。 以添加一个todo的Action为例: { type:'add_todo',data:'我要去跑步' }
这样就定义了一个添加一条todo的Action,然后就能通过某个行为去触发这个Action,由这个Action携带的数据(data)去更新store(state/reducer): store.dispatch({ type:'add_todo',data:'your data' })
const ADD_TODO = 'add_todo';
let addTodo = (data='default data') => {
return {
type: ADD_TODO,data: data
}
}
//触发action
store.dispatch(addTodo());
更改之后,代码清晰多了,如果有多个行为触发同一个Action,只要调用一下函数 但是,这样的Action Creator 返回的Action 并不是一个标准的Action。在Flux的架构中,一个Action要符合 FSA(Flux Standard 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 提供了 export default function applyMiddleware(...middlewares) {
return (createStore) => (reducer,preloadedState,enhancer) => {
//接收createStore参数
var store = createStore(reducer,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
根据上文的 以一个打印 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');
}
}
看控制台的输出: 应用多个中间件时,中间件调用链中任何一个缺少 异步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 原文:https://github.com/dwqs/blog/issues/35 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |