我的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
。
答案 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
联合的对应元素。它是Action
与Action
的交集,payload
与Action
的交集。而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
。前两个声明描述了两种情况:如果Action
是T
的{{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