该文章仅作为个人笔记
在使用redux的时候,避免不了的就是创建各种action和actionCreator等,有没有什么更方便的方法呢,答案是redux-actions
Installation
npm install--save redux-actions 或 yarn add redux-actions
使用方法
import { createActions, handleActions, combineActions } from 'redux-actions'
const defaultState = { counter: 10 };
const { increment, decrement } = createActions({
INCREMENT: amount => ({ amount }),
DECREMENT: amount => ({ amount: -amount })
});
const reducer = handleActions({
[combineActions(increment, decrement)](state, { payload: { amount } }) {
return { ...state, counter: state.counter + amount };
}
}, defaultState);
export default reducer;
Api 构成
- createAction
createAction(type)
createAction(type, payloadCreator)
createAction(type, payloadCreator, metaCreator) - createActions
createActions(actionMap)
createActions(actionMap, ...identityActions) - handleAction
handleAction(type, reducer, defaultState)
handleAction(type, reducerMap, defaultState) - handleActions
handleActions(reducerMap, defaultState) - combineActions
combineActions(...types)
redux actions的API相对数量少而且简单,下面进行逐一解释。
首先<code>createAction(s)</code>其实就是
action creator的一个包装,返回值是带有payload的标准action,<code> createAction</code>如果叫做<code> createActionCreator</code>可能会更贴切一点。-
<code>createAction(type)</code>,
type是createAction方法的唯一必须参数,type必须是一个String或这个有toString方法的一个对象,作为action的type出现。
// Example
export const increment = createAction('INCREMENT')
export const decrement = createAction('DECREMENT')increment() // { type: 'INCREMENT' } decrement() // { type: 'DECREMENT' } increment(10) // { type: 'INCREMENT', payload: 10 } decrement([1, 42]) // { type: 'DECREMENT', payload: [1, 42] }
注:如果payload是一个Error object, redux-actions将会自动设置action.error为true
// EXAMPLE
const noop = createAction('NOOP');
const error = new TypeError('not a number');
expect(noop(error)).to.deep.equal({
type: 'NOOP',
payload: error,
error: true
});
当应在<code>handleAction(s)</code>中使用时,<code>createAction</code>会直接返回他的type。
//EXAMPLE
const noop = createAction('INCREMENT');
// As parameter in handleAction:
handleAction(noop, {
next(state, action) {...},
throw(state, action) {...}
});
// As object key in handleActions:
const reducer = handleActions({
[noop]: (state, action) => ({
counter: state.counter + action.payload
})
}, { counter: 0 });
这不是什么神奇的魔法,查看createAction和HandlerAction的源码,就会发现
// createAction.js
export default function createAction(type, payloadCreator = identity, metaCreator) {
...此处省略
const typeString = type.toString();
const actionCreator = (...args) => {
...此处省略
return action;
};
// actionCreator实现了toString方法
actionCreator.toString = () => typeString;
return actionCreator;
}
// handleAction.js
export default function handleAction(type, reducer = identity, defaultState) {
// handlerAction中调用了toString
const types = type.toString().split(ACTION_TYPE_DELIMITER);
...此处省略
return (state = defaultState, action) => {
const { type: actionType } = action;
if (!actionType || !includes(types, actionType.toString())) {
return state;
}
return (action.error === true ? throwReducer : nextReducer)(state, action);
};
}
创建一次性的actionCreator
// EXAMPLE
createAction('ADD_TODO')('Use Redux');
-
<code>createAction(type, payloadCreator)</code>,<code>payloadCreator</code>必须是一个函数, <code>undefined</code>, 或者 <code>null</code>. 如果<code>payloadCreator</code>是<code>undefined</code> 或 <code>null</code>, 着
payload会使用lodash/identity一个会把传入的第一个参数直接返回的函数。
// EXAMPLElet noop = createAction('NOOP', amount => amount); // same as noop = createAction('NOOP'); expect(noop(42)).to.deep.equal({ type: 'NOOP', payload: 42 }); -
<code>createAction(type, payloadCreator, metaCreator)</code>,<code>metaCreator</code> 是一个可选的函数,它为<code>payload</code>创建<code>matadata</code>. 他和<code>payload creator</code>接收同样的参数,但是他的返回值会做为
action中meta字段的值。 如果metaCreator是
undefined或不是一个函数,action中的mata他字段会被删除。
// EXAMPLEconst updateAdminUser = createAction('UPDATE_ADMIN_USER', (updates) => updates, () => ({ admin: true }) ) updateAdminUser({ name: 'Foo' }) // { // type: 'UPDATE_ADMIN_USER', // payload: { name: 'Foo' }, // meta: { admin: true }, // } createActionsimport { createActions } from 'redux-actions';
createActions(actionMap),actionMap是一个对象,他可以有可递归(嵌套)的结构, action types作为key, 值 必须是 下面的一种
- 一个
payload creator函数 - 一个数组,包含
payload和meta的creator函数,必须保证顺序。-
mata creator是必须的,如果不需要,请使用上面的选项。
-
- 一个
actionMap
// EXAMPLE
createActions({
ADD_TODO: todo => ({ todo }) // payload creator,
REMOVE_TODO: [
todo => ({ todo }), // payload creator
(todo, warn) => ({ todo, warn }) // meta creator
]
});
如果 actionMap 有一个嵌套结构,嵌套的出口是他的值是payload和mata的creator,并且他的action type会被合并,以/分割,如下例子:
// EXAMPLE
const actionCreators = createActions({
APP: {
COUNTER: {
INCREMENT: [
amount => ({ amount }),
amount => ({ key: 'value', amount })
],
DECREMENT: amount => ({ amount: -amount }),
SET: undefined // given undefined, the identity function will be used
},
NOTIFY: [
(username, message) => ({ message: `${username}: ${message}` }),
(username, message) => ({ username, message })
]
}
});
expect(actionCreators.app.counter.increment(1)).to.deep.equal({
type: 'APP/COUNTER/INCREMENT',
payload: { amount: 1 },
meta: { key: 'value', amount: 1 }
});
expect(actionCreators.app.counter.decrement(1)).to.deep.equal({
type: 'APP/COUNTER/DECREMENT',
payload: { amount: -1 }
});
expect(actionCreators.app.counter.set(100)).to.deep.equal({
type: 'APP/COUNTER/SET',
payload: 100
});
expect(actionCreators.app.notify('yangmillstheory', 'Hello World')).to.deep.equal({
type: 'APP/NOTIFY',
payload: { message: 'yangmillstheory: Hello World' },
meta: { username: 'yangmillstheory', message: 'Hello World' }
});
注:你亦可以自定义分割type的字符串, createActions({ ... }, 'INCREMENT', { namespace: '--' }),默认是 / 。
-
createActions(actionMap, ...identityActions),identityActions是一个可选的字符串列表,他会被用来做action type; 这些action types会使用identity payload creator。
// EXAMPLEconst { actionOne, actionTwo, actionThree } = createActions({ // function form; payload creator defined inline ACTION_ONE: (key, value) => ({ [key]: value }), // array form ACTION_TWO: [ (first) => [first], // payload (first, second) => ({ second }) // meta ], // trailing action type string form; payload creator is the identity }, 'ACTION_THREE'); expect(actionOne('key', 1)).to.deep.equal({ type: 'ACTION_ONE', payload: { key: 1 } }); expect(actionTwo('first', 'second')).to.deep.equal({ type: 'ACTION_TWO', payload: ['first'], meta: { second: 'second' } }); expect(actionThree(3)).to.deep.equal({ type: 'ACTION_THREE', payload: 3, });
handleAction(s)
$ import { handleAction } from 'redux-actions';
-
handleAction(type, reducer, defaultState),reducer是一个函数, 他会同时处理normal actions和failed actions. (failed action就像promise的rejected.) 如果你能确定,永远不会产生failed actions,就可以使用这个handleAction。如果reducer是undefined,会使用identity代替。第三个参数defaultState是必须的,在reducer是undefined时作为identity的参数。
// EXAMPLEhandleAction('APP/COUNTER/INCREMENT', (state, action) => ({ counter: state.counter + action.payload.amount, }), defaultState); -
handleAction(type, reducerMap, defaultState),你也可以使用包含了next()和throw()的reducerMap,灵感来自ES6 generator。如果reducerMap是undefined,会使用identity代替。如果next()或throw()任何一个是undefined或null, 就会使用identity函数代替。
// EXAMPLEhandleAction('FETCH_DATA', { next(state, action) {...}, throw(state, action) {...}, }, defaultState); -
handleActions(reducerMap, defaultState),handleActions 就是通过 handleAction() 创建多个 reducers,然后把他们合并为一个reducer,并且处理多个 actions。
// EXAMPLEconst reducer = handleActions({ INCREMENT: (state, action) => ({ counter: state.counter + action.payload }), DECREMENT: (state, action) => ({ counter: state.counter - action.payload }) }, { counter: 0 }); -
combineActions合并任意数量的action type或action creator,combineActions(...types),你可以用这个方法让多个action使用一个reducer函数,types是strings,symbols, 或action creators的列表。import { combineActions } from 'redux-actions';。
// EXAMPLEconst { increment, decrement } = createActions({ INCREMENT: amount => ({ amount }), DECREMENT: amount => ({ amount: -amount }), }) const reducer = handleAction(combineActions(increment, decrement), { next: (state, { payload: { amount } }) => ({ ...state, counter: state.counter + amount }), throw: state => ({ ...state, counter: 0 }), }, { counter: 10 }) expect(reducer(undefined, increment(1)).to.deep.equal({ counter: 11 }) expect(reducer(undefined, decrement(1)).to.deep.equal({ counter: 9 }) expect(reducer(undefined, increment(new Error)).to.deep.equal({ counter: 0 }) expect(reducer(undefined, decrement(new Error)).to.deep.equal({ counter: 0 })
把 combineActions 和 handleActions 结合起来的用法如下:
// EXAMPLE
const { increment, decrement } = createActions({
INCREMENT: amount => ({ amount }),
DECREMENT: amount => ({ amount: -amount })
});
const reducer = handleActions({
[combineActions(increment, decrement)](state, { payload: { amount } }) {
return { ...state, counter: state.counter + amount };
}
}, { counter: 10 });
expect(reducer({ counter: 5 }, increment(5))).to.deep.equal({ counter: 10 });
expect(reducer({ counter: 5 }, decrement(5))).to.deep.equal({ counter: 0 });
expect(reducer({ counter: 5 }, { type: 'NOT_TYPE', payload: 1000 })).to.equal({ counter: 5 });
expect(reducer(undefined, increment(5))).to.deep.equal({ counter: 15 });