相关文档
目的
Actions结构约定
示例
工具库
flux-standard-action
isFSA(action)
isError(action)
redux-actions
createAction
createActions
handleAction
handleActions
combineActions
相关文档
目的
约定Flux action的数据结构有助于前端模块化的处理^[1]^。
注1:
对于Flux,以及Redux而言,其最初的设计动机都是为控制数据的修改操作范围(React组件中的state),为达到这个目的,Flux抽象出Action的概念,将View和Store彻底解耦,所有对状态数据的修改,均只能通过Action进行。在Flux的设计(以及集成其设计并加以改进的Redux)中,Action即为一个普通的Json对象,其属性字段可任意定制(Action的属性在Reducer中引用,也就是说,在不考虑Reducer代码的可读/维护性情况下,每个Action可以完全不同)。Flux及Redux并无特定要求,但从代码的可读性以及可维护性考虑,Action对象的定义仍应遵循一定规则。
Actions结构约定
一个Flux标准Action应满足以下标准:
- 必须为标准的Json对象。
- 必须包含type属性。
- 可以包含error、payload、meta属性,除这些属性外,不应再包含其他属性。
各属性含义如下:
属性名 | 属性含义 |
---|---|
type | type属性为Action对象的标识,值应为string,对type值的判断应使用严格对等(===)。 |
error | 若Action运行时可能产生异常时,error属性应设置为true,此时payload属性值应为一个错误对象,当error属性值为true之外的任何值(包括undefined和null)时,payload应为错误对象外的其他值,且应确保Action运行不会出现异常。 |
payload | 当error为true时,payload应为错误对象,否则payload可以为错误对象之外的任意值,此时payload可用于描述Action除type、status之外的任意信息(如附加参数)。 |
meta | 可以为任意类型的值,可用于补充payload信息。 |
示例
标准Flux Action代码结构如下:
{
type : "ADD_TODO",
payload : {
text : "Do something.",
data : ["some value"]
},
meta : {
text : "flux standard action"
}
}
可能产生异常的标准Flux Action代码结构如下:
{
type : "ADD_TODO",
payload : new Error(),
error : true
}
工具库
flux-standard-action
可用于判断Action是否符合Flux标准,提供以下API:
isFSA(action)
引入方式:
import {isFSA} from "flux-standard-action";
当action为符合Flux标准的Action时,返回true。
isError(action)
引入方式:
import {isError} from "flux-standard-action";
当action会产生异常时返回true。
redux-actions
文档地址:
- GitHub。
示例代码:
import {createActions, handleActions, combineActions} from "redux-actions";
const defaultState = {
counter : 10
};
const {increment, decrement} = createActions({
INCREMENT : (amount = 1) => ({ amount }),
DECREMENT : (amount = 1) => ({ amount : -amount })
});
const reducer = handleAction(
{
[combineActions(increment, decrement)] : (
state,
{ payload : {amount}}
) => {
return { ...state, counter : state.counter + amount };
}
},
defaultState
);
export default reducer;
createAction
方法声明:
createAction(
type,
payloadCreator = identity,
?metaCreator
)
其中:
- type为必须参数,即对应生成的Action中type属性。
- payloadCreator和metaCreator为可选参数,其中,若payloadCreator不指定,则使用默认的处理,即payload即为输入参数。
- 当调用Action,并将payload对应参数设置为Error对象时,redux-actions会自动将action.error设置为true,如以下的代码所示:
const noop = createAction("NOOP");
const error = new TypeError("not a number");
expect(noop(error)).to.deep.equal({
type : "NOOP",
payload : error,
error : true
});
createActions
方法声明:
createActions(
actionMap,
?...identityActions
)
其中:
- actionMap:递归结构的对象,每个属性key为action type,每个属性value仅可为以下几种类型的值:
- function:定位为createAction方法中的payload creator。
- array:必须依次包含payload creator、meta creator。
- actionMap。
当actionMap为递归结构(即包含多层)时,最终产生的Action对象type值为其在actionMap中对应的属性key值,加上其父节点的type值合并形成,每层节点的type值默认用"/"分割,如下:
const actionCreators = createActions({
APP : {
COUNTER : {
INCREMENT : [
amount => ({amount}),
amount => ({
key : "value",
amount
})
],
DECREMENT : amount => ({ amount : -amount }),
SET : undefined
},
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(acountCreators.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("someuser", "Hello World")).to.deep.equal({
type : "APP/NOTIFY",
payload : {
message : "someuser : Hello World"
},
meta : {
username : "someuser",
message : "Hello World"
}
});
在上述的示例中,有两点比较特殊:
- 对于每个Action type的生成,如最初所述,若调用调用createActions方法时,传入递归结构的参数,那么type值将会是从根节点开始,沿路径依次合并的值,每级之间以特定分隔符切分,默认分割符为"/",此分隔符也可以在方法的最后一个参数处指定,对应参数名称为namespace,如下:
createActions({...}, "INCREMENT", { namespace : "--"}); //将type值的分隔符设置为--
- 在上述的示例代码中,"SET" Action未指定payload creator,此时将采用默认的处理,如以下源码所示:
//createAction.js
import invariant from 'invariant';
import isFunction from './utils/isFunction';
import identity from './utils/identity';
import isNull from './utils/isNull';
export default function createAction(
type,
payloadCreator = identity, //若不指定payloadCreator,则使用utils/identity中定义的默认处理方式,即value => value
metaCreator
) {
invariant(
isFunction(payloadCreator) || isNull(payloadCreator),
'Expected payloadCreator to be a function, undefined or null'
);
const finalPayloadCreator =
isNull(payloadCreator) || payloadCreator === identity
? identity
: (head, ...args) =>
head instanceof Error ? head : payloadCreator(head, ...args);
const hasMeta = isFunction(metaCreator);
const typeString = type.toString();
const actionCreator = (...args) => {
const payload = finalPayloadCreator(...args);
const action = { type };
if (payload instanceof Error) {
action.error = true;
}
if (payload !== undefined) {
action.payload = payload;
}
if (hasMeta) {
action.meta = metaCreator(...args);
}
return action;
};
actionCreator.toString = () => typeString;
return actionCreator;
}
handleAction
方法声明:
handleAction(
type,
reducer | reducerMap = Identity,
defaultState
)
其中:
- type即对应Action的type属性。
- reducer/reducerMap即为响应Action的处理方法。如不给定,则使用默认的处理方法(value => value)。
- defaultState对应调用reducer的state参数,其定义为state参数的默认值。
此处需要注意的是,若第2个参数使用reducerMap的形式,则应手动指定next()、throw()方法,即handleAction方法中的reducerMap并不是任意格式的Json对象,此点和createActions中的actionMap参数有差别。
使用reducerMap的示例代码如下:
handleAction(
"FETCH_DATA",
{
next(state, action) {....},
throw(state, action){....}
},
defaultState
);
- 当next()、throw()方法中任一项未定义时,均视为reducerMap未定义,并使用默认方式处理。
- 当action.error为true时,调用throw()方法,否则调用next()方法。
handleActions
方法声明:
handleActions(reducerMap, defaultState)
类似createActions,此方法创建针对多个action的reducer。其中,reducerMap的参数类型可以为以下类型:
- Map:此时key为action.type,value为reducer。
- Json:此时属性名为action.type,value为reducer。此时参数允许为递归结构,action.type的值为从根到最终叶子节点的路径,每级以固定分隔符切分,默认分隔符为/。
combineActions
方法声明:
combineActions(...types)
此方法合并多个Action type或Action creator,type参数的取值可以为action type字符串,或者action creator。
示例代码:
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});
所有评论(0)