此问题的规范答案取决于您的具体用例。我将假设您需要 Action 来准确评估您编写的类型;也就是说,type: "DO_X" 的对象 not 具有任何类型的 payload 属性。这意味着createAction("DO_X") 应该是零参数的函数,而createAction("DO_Y") 应该是单个string 参数的函数。我还将假设您希望自动推断 createAction() 上的任何类型参数,这样您就不需要为 Blah 的任何值指定 createAction<Blah>("DO_Z")。如果解除这些限制中的任何一个,您可以将解决方案简化为@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 类型是一个对象,其键是每个 Action 的 type,其值是 Action 联合的对应元素。它是Actions 与payloads 和Action 与payloads 的交集。而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 对应于您正在创建的Action 的type。前两个声明描述了这两种情况:如果T 是Action 的type 而没有payload,则返回类型是返回正确类型Action 的零参数函数。否则,它是一个单参数函数,它采用正确的payload 类型并返回正确的Action 类型。实现(第三个签名和正文)与您的类似,只是如果没有传入payload,它不会将payload 添加到结果中。
全部完成!我们可以看到它按预期工作:
var x = createAction("DO_X")(); // x: { type: "DO_X"; }
var y = createAction("DO_Y")("foo"); // y: { type: "DO_Y"; payload: string; }
var z = createAction("DO_Z")(5); // z: { type: "DO_Z"; payload: number; }
createAction("DO_X")('foo'); // too many arguments
createAction("DO_X")(undefined); // still too many arguments
createAction("DO_Y")(5); // 5 is not a string
createAction("DO_Z")(); // too few arguments
createAction("DO_Z")(5, 5); // too many arguments
您可以在on the TypeScript Playground 看到这一切。希望对你有效。祝你好运!