Typecheck createAction Redux助手

时间:2017-08-02 15:19:34

标签: typescript redux union flux

我的Action类型定义如下:

type Action =
    { type: 'DO_X' }
    |
    { type: 'DO_Y', payload: string }
    |
    { type: 'DO_Z', payload: number }

这是一种联合类型,其中每个成员都是有效的行为。

现在我想创建一个接受createAction的函数type并返回一个接受payload的新函数。

const doZ = createAction('DO_Z')
console.log(doZ(42)) // { type: 'DO_Z', payload: 42 }

这是我目前的实施:

const createAction = (type: Action['type']) =>
  (payload?: any) =>
    ({ type, payload })

它像我想要的那样强调type。我怎样才能检查payload?我希望payload根据type匹配正确操作的类型。例如,使用doZ调用时,string会失败,因为它payload表示它只接受number

2 个答案:

答案 0 :(得分:4)

这个问题的规范答案取决于您的确切用例。我将假设您需要Action来准确评估您所写的类型;也就是说,type: "DO_X"的对象具有任何类型的payload属性。这意味着createAction("DO_X")应该是零参数的函数,而createAction("DO_Y")应该是单个string参数的函数。我还假设您希望自动推断createAction()上的任何类型参数,因此您不需要为createAction<Blah>("DO_Z")的任何值指定Blah }。如果取消这些限制中的任何一个,您可以将解决方案简化为@Arnavion给出的解决方案。

TypeScript不喜欢来自属性 values 的映射类型,但是很高兴从属性 keys 这样做。因此,让我们以一种方式构建Action类型,为我们提供编译器可以用来帮助我们的类型。首先,我们描述每种操作类型的有效负载,如下所示:

type ActionPayloads = {
  DO_Y: string;
  DO_Z: number;
}

我们还介绍没有有效负载的任何Action类型:

type PayloadlessActionTypes = "DO_X" | "DO_W";

(我添加了'DO_W'类型只是为了展示它是如何工作的,但你可以删除它。)

现在我们终于可以表达Action

type ActionMap = {[K in keyof ActionPayloads]: { type: K; payload: ActionPayloads[K] }} & {[K in PayloadlessActionTypes]: { type: K }};
type Action = ActionMap[keyof ActionMap];

ActionMap类型是一个对象,其键是每个type的{​​{1}},其值是Action联合的对应元素。它是ActionAction的交集,payloadAction的交集。而payload只是Action的值类型。验证ActionMap是否符合预期。

我们可以使用Action来帮助我们输入ActionMap函数。这是:

createAction()

这是一个重载函数,其类型参数function createAction<T extends PayloadlessActionTypes>(type: T): () => ActionMap[T]; function createAction<T extends keyof ActionPayloads>(type: T): (payload: ActionPayloads[T]) => ActionMap[T]; function createAction(type: string) { return (payload?: any) => (typeof payload === 'undefined' ? { type } : { type, payload }); } 对应于您正在创建的T type。前两个声明描述了两种情况:如果ActionT的{​​{1}}而没有type,则返回类型是零参数函数,返回正确的类型Action。否则,它是一个单参数函数,它采用正确类型的payload并返回正确类型的Action。实现(第三个签名和正文)与您的类似,但如果没有传递payload则不会将Action添加到结果中。

全部完成!我们可以看到它按预期工作:

payload

您可以在https://bitbucket.org/site/master/issues/12750/allow-multiple-steps?_ga=2.262592203.639241276.1502122373-95544429.1500927287中看到这一切。希望对你有效。祝你好运!

答案 1 :(得分:1)

详细,但这有效:

type XAction = { type: 'DO_X', payload: undefined }; 
type YAction = { type: 'DO_Y', payload: string }; 
type ZAction = { type: 'DO_Z', payload: number }; 

type Action = XAction | YAction | ZAction;

const createAction = <T extends Action>(type: T['type']) =>
    (payload: T['payload']) =>
        ({ type, payload });

// Do compile:

createAction<XAction>("DO_X")(undefined);
createAction<YAction>("DO_Y")("foo");
createAction<ZAction>("DO_Z")(5);

// Don't compile:

createAction<XAction>("DO_X")(5); // Expected `undefined`, got number
createAction<YAction>("DO_Y")(5); // Expected string, got number
createAction<ZAction>("DO_X")(5); // Expected `"DO_Z"`, got `"DO_X"`

更简单的方法(不强制createAction的类型参数):

type Action = { type: 'DO_X', payload: undefined } | { type: 'DO_Y', payload: string } | { type: 'DO_Z', payload: number };

createAction("DO_Y")("foo");

很遗憾地允许createAction<YAction>("DO_Y")(5)等进行编译,因为T始终被推断为Action,因此payload参数为string|number|undefined