Table of Contents generated with DocToc
redux-actions ,一个帮助 redux 应用快速创建 actions 、reducers 的辅助类函数库。通过它,可以摒弃掉传统的 redux action/reducer 定义方法,更加快速有效的创建一个 redux 应用。
在这之前,我们可以先看看在传统 redux 中是如何定义 action 和 reducer 的:
// actions.js
export const INITIAL = 'INITIAL';
export const initial = (initialValue) => {
return {
type: INITIAL,
state: initialValue
}
};
// reducers.js
import { combineReducers } from 'redux';
import * as ACTIONS from '../actions.js';
const state = {
initialValue: 0
};
const initialValue = (initialValue = state.initialValue, action) {
switch(action.type) {
case ACTIONS.INITIAL:
return action.state;
default:
return initialValue;
}
};
export default combineReducers(
initialState
);
而在使用了 redux-actions 之后,整个过程可以得到极大的简化:
// actions.js
const initial = createAction('INITIAL');
export default {
initial
}
const state = {
initialValue: 0
};
const reducer = handleActions({
INITIAL(state, action) {
return ({
...state,
initialValue: action.playload
});
}
}, state);
export default reducers;
通过 createAction
API,可以快速的创建方法。例如,
const initial = createAction('INITIAL');
// initial 方法就相当于
const initial = (value) => {
return {
type: INITIAL,
payload: value
}
};
// 也可以手动代入方法
const initial = createAction('INITIAL', () => null);
// 相当于
const initial = () => {
return {
type: INITIAL,
payload: null
}
};
const initial = createAction('INITIAL', (name, email) => ({name, email}));
// 相当于
const initial = (name, email) => {
return {
type: INITIAL,
payload: {
name,
email
}
}
};
同时也提供了 createActions
API,用来批量创建方法:
const {
initial,
setName,
setEmail
} = createActions(
'INITIAL',
'SET_NAME',
'SET_EMAIL'
);
// 如果要给某个方法指定自己的函数,比如
// const initial = createAction('INITIAL', () => null)
const {
initial,
setName,
setEmail
} = createActions({
'INITIAL': () => null
},
'SET_NAME',
'SET_EMAIL'
);
// identity 返回一个基本的函数:
// value => value
import identity from 'lodash/identity';
import isFunction from 'lodash/isFunction';
import isUndefined from 'lodash/isUndefined';
import isNull from 'lodash/isNull';
// 一个用于验证的库
// invariant(someTruthyVal, 'This will not throw');
// No errors
import invariant from 'invariant';
export default function createAction(type, payloadCreator = identity, metaCreator) {
// payloadCreator 为给 action 指定的自定义方法,必须是个函数或 undefined
invariant(
isFunction(payloadCreator) || isNull(payloadCreator),
'Expected payloadCreator to be a function, undefined or null'
);
// 如果不传入 payloadCreator,则使用 identity 替代:
// const identity = (value) => value;
const finalPayloadCreator = isNull(payloadCreator)
? identity
: payloadCreator;
// actionCreator 是 createAction 最终返回的函数
const actionCreator = (...args) => {
const hasError = args[0] instanceof Error;
const action = {
type
};
const payload = hasError ? args[0] : finalPayloadCreator(...args);
if (!isUndefined(payload)) {
action.payload = payload;
}
if (hasError || payload instanceof Error) {
action.error = true;
}
if (isFunction(metaCreator)) {
action.meta = metaCreator(...args);
}
return action;
};
// 复写 toString 方法,确保在对该方法调用 toString 时,输出的是 action.type
actionCreator.toString = () => type.toString();
return actionCreator;
}
更多使用实例:createActions(?actionsMap, ?...identityActions)
// 如上面的例子所示
// createActions 的参数可以是多个字符串,但如果要自定义 action 的处理函数,则第一个参数为一个对象
// 因此第一个参数比较特殊(Object 或 String),之后再可以有不显个数个相同类型(String)的参数
export default function createActions(actionsMap, ...identityActions) {
invariant(
identityActions.every(isString) &&
(isString(actionsMap) || isPlainObject(actionsMap)),
'Expected optional object followed by string action types'
);
// 如果第一个参数也是 String,则所有参数都是 String。将它们组成数组代入函数中
if (isString(actionsMap)) {
return fromIdentityActions([actionsMap, ...identityActions]);
}
// 否则将第一个函数特殊对待
return { ...fromActionsMap(actionsMap), ...fromIdentityActions(identityActions) };
}
function isValidActionsMapValue(actionsMapValue) {
if (isFunction(actionsMapValue)) {
return true;
} else if (isArray(actionsMapValue)) {
const [payload = identity, meta] = actionsMapValue;
return isFunction(payload) && isFunction(meta);
}
return false;
}
// 将
// {
// 'INITIAL': () => null,
// 'SET_DATA': (name, email) => ({ name, email }),
// 'ACTION_TWO': [
// (first) => [first], // payload
// (first, second) => ({ second }) // meta
// ],
// }
// 转换为
// {
// 'initial': createAction('INITIAL', () => null),
// 'setData': createAction('SET_DATA', (name, email) => ({ name, email })),
// 'actionTwo': createAction('ACTION_TWO', (first) => [first], (first, second) => ({ second }))
// }
//
// camelCase 的作用是将形如 SET_DATA 的 String 转换为 setData
function fromActionsMap(actionsMap) {
return Object.keys(actionsMap).reduce((actionCreatorsMap, type) => {
const actionsMapValue = actionsMap[type];
invariant(
isValidActionsMapValue(actionsMapValue),
'Expected function, undefined, or array with payload and meta ' +
`functions for ${type}`
);
const actionCreator = isArray(actionsMapValue)
? createAction(type, ...actionsMapValue)
: createAction(type, actionsMapValue);
return { ...actionCreatorsMap, [camelCase(type)]: actionCreator };
}, {});
}
// 将 ['INITIAL', 'ACTION_ONE', 'ACTION_TWO'] 转为
// {
// initial: createAction('INITIAL'),
// actionOne: createAction('ACTION_ONE'),
// actionTwo: createAction('ACTION_TWO'),
// }
function fromIdentityActions(identityActions) {
return fromActionsMap(identityActions.reduce(
(actionsMap, actionType) => ({ ...actionsMap, [actionType]: identity })
, {}));
}
通过 handleActions
一次性处理所有的 action handler,不再需要传统的 combineReducers
import { handleActions } from 'redux-actions';
const reducer = handleActions({
INCREMENT: (state, action) => ({
counter: state.counter + action.payload
}),
DECREMENT: (state, action) => ({
counter: state.counter - action.payload
})
}, { counter: 0 });
export default reducer;
在源码中引用了 reduce-reducers
库,目的是将多个单一的 reducer 通过 reduce
的方式转为一个 reducer。
通过官方实例可以看出,其实本质就是将参数依次代入每个 reducer 进行调用,返回最终的结果。因此,核心和 redux 的 combineReducer
是一样的:将 action 依次代入每个 reducer 进行调用,总有一个 reducer 可以处理当前的 action.type,否则返回 default。
reduce-reducers 实例:
// reduce-reducers 官方实例
import reduceReducers from 'reduce-reducers';
const reducer = reduceReducers(
(prev, curr) => ({...prev, A: prev.A + curr}),
(prev, curr) => ({...prev, B: prev.B * curr}),
);
expect(reducer({ A: 1, B: 2 }, 3)).to.deep.equal({ A: 4, B: 6 });
expect(reducer({ A: 5, B: 8 }, 13)).to.deep.equal({ A: 18, B: 104 });
handleActions 源码:
// handleActions
import handleAction from './handleAction';
import ownKeys from './ownKeys';
import reduceReducers from 'reduce-reducers';
// handlers 为一个 object
// {
// KEY: actionHandler
// }
// 将每个 KEY: actionHandler 键值对都通过 handleAction 处理,返回结果都是形如
// (state, action) => newState 的函数
// 并通过 reduce-reducers 合为一个
export default function handleActions(handlers, defaultState) {
const reducers = ownKeys(handlers).map(type =>
handleAction(
type,
handlers[type],
defaultState
)
);
const reducer = reduceReducers(...reducers);
return (state, action) => reducer(state, action);
}
handleAction(type, reducer | reducerMap = Identity, defaultState)
使用方式:
handleAction(
// action type
'FETCH_DATA',
// reducer
// 你可以分别处理 next(成功)和throw(失败)两种情况,也可以同时处理
{
next(state, action) {...},
throw(state, action) {...}
},
// default state
defaultState
);
源码:
import isFunction from 'lodash/isFunction';
import isPlainObject from 'lodash/isPlainObject';
import identity from 'lodash/identity';
import isNil from 'lodash/isNil';
import isUndefined from 'lodash/isUndefined';
import includes from 'lodash/includes';
import invariant from 'invariant';
import { ACTION_TYPE_DELIMITER } from './combineActions';
export default function handleAction(actionType, reducer = identity, defaultState) {
const actionTypes = actionType.toString().split(ACTION_TYPE_DELIMITER);
// reducer 要么是一个函数,next/throw 两种情况会被统一对待
// 要么是个形如 { next() {}, throw() {} } 的函数,依次处理成功和失败的情况
invariant(
!isUndefined(defaultState),
`defaultState for reducer handling ${actionTypes.join(', ')} should be defined`
);
invariant(
isFunction(reducer) || isPlainObject(reducer),
'Expected reducer to be a function or object with next and throw reducers'
);
const [nextReducer, throwReducer] = isFunction(reducer)
? [reducer, reducer]
: [reducer.next, reducer.throw].map(aReducer => (isNil(aReducer) ? identity : aReducer));
// 返回真正的 reducer,在内部进行类型检查,跳过不处理的 action
return (state = defaultState, action) => {
const { type } = action;
if (type && !includes(actionTypes, type.toString())) {
return state;
}
// 否则在没有错误的情况下使用 nextReducer 进行处理,返回修改的 state
return (action.error === true ? throwReducer : nextReducer)(state, action);
};
}