从休息参数推断条件元组

时间:2019-12-17 02:15:44

标签: typescript types tuples

是否可以从键/值映射中推断出元组?

基本上可以是从联合类型到元组的转换(例如https://github.com/microsoft/TypeScript/issues/13298#issuecomment-482330241

但是我希望有一个更优雅,更易于编译的解决方案,例如手册(http://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-0.html#example-2)中建议的元组函数

interface ParamMapping {
    a: boolean;
    b: string;
    c: 'foo' | 'bar';
}

declare function param<K extends keyof ParamMapping>(...name: K[]): ParamMapping[K];
// posible solutions:
// declare function param<K extends keyof ParamMapping>(...name: K[]): ParamMapping[...K];
// declare function param<K extends keyof ParamMapping>(...name: K[]): ...ParamMapping[K];
// declare function param<K extends (keyof ParamMapping)[]>(...name: K): for P of K : ParamMapping[K];
declare function param(...name: string[]): unknown[];

// typed as unknown[]
const p1 = param('baz', 'qux');

// *should be* typed as [boolean, string]
const p2 = param('a', 'b');

// *should be* typed as [string, 'foo' | 'bar']
const p3 = param('b', 'c');

// *should be* typed as [boolean, string, 'foo' | 'bar']
const p4 = param('a', 'b', 'c');

Playground here

2 个答案:

答案 0 :(得分:3)

我认为您想通过将第一个重载签名更改为mapped tuple types

declare function param<T extends Array<keyof ParamMapping>>(...name: T): {
    [I in keyof T]: ParamMapping[Extract<T[I], keyof ParamMapping>]
};

在这里,name的类型为T,它本身就是我们需要映射的数组或键的元组。如果对于某个键类型为name,我们将K[]作为类型K,则编译器将不会跟踪该顺序,因为K[]不是元组。请注意,编译器无法理解I始终是类似数字的索引,因此我需要编写Extract<T[I], keyof ParamMapping>来获取可以用ParamMapping进行索引的内容。

您可以验证它的行为是否符合您的要求:

const p1 = param('baz', 'qux');
// const p1: unknown[]

const p2 = param('a', 'b');
// const p2: [boolean, string]

const p3 = param('b', 'c');
// const p3: [string, "foo" | "bar"]

const p4 = param('a', 'b', 'c');
// const p4: [boolean, string, "foo" | "bar"]

好的,希望能有所帮助。祝你好运!

Playground link

答案 1 :(得分:0)

编辑实际上,我的“解决方案”在很多方面都错了

这是一个改进且有效的示例。某些地方有些黑,但这确实可以做到:

// Base interface that can be latter augmented
interface ParamMapping {
    a: boolean;
    b: string; 
    c: 'foo' | 'bar';
}

// handle every other cases as unknown typed
interface ExtendedParamMapping extends ParamMapping {
    [P: string]: unknown;
}

// hack to handle autocomplete on: "foo" | "bar" | string
type UnknownMapping = string & { _?: never }

// Yes. Named like this.
type Jcalz<T> = {
    [I in keyof T]: ExtendedParamMapping[Extract<T[I], keyof ParamMapping>]
};

declare function param<T extends Array<keyof ParamMapping>, U extends T | UnknownMapping[]>(...name: U | T): Jcalz<U>;

const p1 = param('baz', 'qux');
// const p1: [unknown, unknown]

const p2 = param('a', 'b');
// const p2: [boolean, string]

const p3 = param('b', 'c');
// const p3: [string, "foo" | "bar"]

const p4 = param('a', 'b', 'c');
// const p4: [boolean, string, "foo" | "bar"]

const p5 = param('a', 'baz');
// const p6: [boolean, unknown]

const p6 = param('foo', 'c');
// const p4: [unknown, "foo" | "bar"]

Playground link


原始答案

(作为帖子答复,不要在注释部分添加代码,因为它可以解决我的问题)

我想我已经找到了如何使用jcalz answer处理自动完成功能:

interface ParamMapping {
    a: boolean;
    b: string;
    c: 'foo' | 'bar';
}

type UnknownMapping = string & { _?: never }

// Yes. Named like this.
type Jcalz<T> = {
    [I in keyof T]: ParamMapping[Extract<T[I], keyof ParamMapping>]
};

type MappedType<T> = T extends keyof ParamMapping ? Jcalz<T> : unknown;

declare function param<T extends Array<keyof ParamMapping>>(...name: T | UnknownMapping[]): MappedType<T>;
// declare function param(...name: string[]): unknown[]; // not needed anymore

const p1 = param('baz', 'qux');
// const p1: unknown[]
// "basic" string works!

const p2 = param('a', 'b');
// const p2: [boolean, string]
// autocomplete works!

不需要其他重载,原始接口保持不变,并且自动完成功能可以正常工作。 (从这里https://github.com/microsoft/TypeScript/issues/29729#issuecomment-460346421得到启发)

Playground link

我将jcalz answer固定为接受状态。