相关文档
目的
Actions结构约定
示例
工具库
flux-standard-action
isFSA(action)
isError(action)
redux-actions
createAction
createActions
handleAction
handleActions
combineActions

相关文档

  1. Flux Standard Action
  2. Redux Actions

目的

约定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应满足以下标准:

  1. 必须为标准的Json对象。
  2. 必须包含type属性。
  3. 可以包含error、payload、meta属性,除这些属性外,不应再包含其他属性。
    各属性含义如下:
属性名属性含义
typetype属性为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

文档地址:

  1. 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
)

其中:

  1. type为必须参数,即对应生成的Action中type属性。
  2. payloadCreator和metaCreator为可选参数,其中,若payloadCreator不指定,则使用默认的处理,即payload即为输入参数。
  3. 当调用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
)

其中:

  1. 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"
    }
});

在上述的示例中,有两点比较特殊:

  1. 对于每个Action type的生成,如最初所述,若调用调用createActions方法时,传入递归结构的参数,那么type值将会是从根节点开始,沿路径依次合并的值,每级之间以特定分隔符切分,默认分割符为"/",此分隔符也可以在方法的最后一个参数处指定,对应参数名称为namespace,如下:
createActions({...}, "INCREMENT", { namespace : "--"}); //将type值的分隔符设置为--
  1. 在上述的示例代码中,"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
)

其中:

  1. type即对应Action的type属性。
  2. reducer/reducerMap即为响应Action的处理方法。如不给定,则使用默认的处理方法(value => value)。
  3. 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的参数类型可以为以下类型:

  1. Map:此时key为action.type,value为reducer。
  2. 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});

转载于:https://my.oschina.net/u/3582233/blog/1924347

Logo

尧米是由西云算力与CSDN联合运营的AI算力和模型开源社区品牌,为基于DaModel智算平台的AI应用企业和泛AI开发者提供技术交流与成果转化平台。

更多推荐