该文章仅作为个人笔记
在使用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 }, // }
createActions
import { 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 });