网站搜索

TypeScript 中的命令和响应泛型


我在为 Command -> Response 通用结构想出一个好结构上遇到了困难。

我的目标是让一个函数根据类型或接口从命令列表中接受一个命令,并使用该接口推断响应类型。

到目前为止,我已经能够根据 type 属性正确推断预期的 data,但我似乎无法理解如何推断泛型的返回类型。

任何关于类似例子的指点都非常欢迎!我还没有找到太多

代码示例:TypeScript Playground

type MappedData<E, T extends keyof E> = E[T];
type MappedType<E> = {
    [K in keyof E]: {
        type: K;
        data: MappedData<E, K>;
    };
}[keyof E];

interface ServerCommandInterface<T> {
     _res?: T;
}

interface TestCommandA extends ServerCommandInterface<{responseA: boolean}> {
    param_a: string;
}

interface TestCommandB extends ServerCommandInterface<{responseB: boolean}> {
    param_b: string;
}

interface Commands {
    command_a: TestCommandA;
    command_b: TestCommandB;
}

function execute<C extends MappedType<Commands>>(command: C): typeof command.data._res {
    // Logic (HTTP call or similar) that would result in _res type
    return null as any;
}

const result = execute({
    type: 'command_a',
    data: {
        param_a: 'param',
    }
});

console.log(result.responseA); // I expect this to be a boolean

答案 #1

所以我认为你想要的是类似下面的辅助类型:

interface Commands {
    command_a: {
        param: { param_a: string },
        response: { responseA: boolean }
    },
    command_b: {
        param: { param_b: string },
        response: { responseB: boolean }
    }
}

这里 Commands 的每个属性都有一个键,对应于传入 execute() 的值的 type 属性。而该属性的值是一个对象类型,包含两个属性:param 和 response。param 属性对应于传入 execute() 的值的 data 属性,而 response 属性对应于 execute() 返回的值。请注意,param 和 response 这两个名字是完全任意的,我们本可以命名为任何想要的名字。这里不会涉及任何 Commands 类型的值;它只是一个帮助类型,使我们能够以简单的方式表达 execute() 的调用签名:

function execute<K extends keyof Commands>(
  command: { type: K, data: Commands[K]['param'] }
): Commands[K]['response'] {
    return null as any;
}

所以 execute() 函数在类型参数 K 上是泛型的,该类型参数被限制为 Commands 的某个键。然后 execute() 的参数有一个 type 属性,其类型为 K,以及一个 data 属性,其类型为 Commands[K]['param']。(我们使用索引访问类型来获取 Commands 中 K 键对应属性的类型,然后获取该属性的 "param" 键对应的属性)。返回类型是 Commands[K]['response']。

让我们看看是否有效:

const result = execute({
    type: 'command_a',
    data: {
        param_a: 'param',
    }
});

这里编译器为 K 推断 "command_a",因此调用签名被指定为

/* function execute<"command_a">(command: {
    type: "command_a";
    data: {
        param_a: string;
    };
}): {
    responseA: boolean;
} */

因此 result 的类型是

/* const result: {
    responseA: boolean;
} */

如预期:

result.responseA === true // okay

代码的 Playground 链接