是否可以从键/值映射中推断出元组?
基本上可以是从联合类型到元组的转换(例如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');
答案 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"]
好的,希望能有所帮助。祝你好运!
答案 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"]
(作为帖子答复,不要在注释部分添加代码,因为它可以解决我的问题)
我想我已经找到了如何使用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得到启发)
我将jcalz answer固定为接受状态。