如何保证泛型可以取泛型?
How to ensure that a generic type can take a generic type?
抱歉标题混乱,因为我不知道如何表达清楚。
我正在尝试实现一种创建泛型类型的方法,稍后将使用它来传递泛型类型。
为了消除混淆,给定这两个接口:
interface Field<T> {
value: T;
hasError: boolean;
}
interface Record {
id: number;
name: string;
}
以及Field
的这些继承
interface FooField<T> extends Field<T> {
foo: () => void;
}
interface BarField<T> extends Field<T> {
bar: () => void;
}
我想生成以下类型的 Record
的新类型:
interface FooRecord {
id: FooField<number>;
name: FooField<string>;
}
interface BarRecord {
id: BarField<number>;
name: BarField<string>;
}
最直观但不正确的方法如下:
type MagicRecord<XField extends Field> = {
[K in keyof Record]: XField<Record[K]>;
};
type FooRecord = MagicRecord<FooField>;
type BarRecord = MagicRecord<BarField>;
我怎样才能做到这一点,如果可能的话,正确地使用打字稿?
你想要 higher kinded types,从 v3.5 开始,TypeScript 不支持它。您在这里所做的任何事情都将是一种解决方法,涉及一定数量的代码重复。
这是一种可能的解决方法。首先,请注意,我不会使用名称 Record
,因为它与 built-in type alias 的名称冲突。我会切换到 MyRecord
:
interface MyRecord {
id: number;
name: string;
}
所以让我们查找 table FieldLookup<T>
,它从通用类型的名称映射到用 T
指定的通用类型本身。此 table 需要为您要支持的 Field<T>
的每个子类型创建一个条目:
interface FieldLookup<T> extends Record<keyof FieldLookup<any>, Field<T>> {
Field: Field<T>;
FooField: FooField<T>;
BarField: BarField<T>;
}
然后我们可以定义MagicRecord<XField>
,它将通用字段的名称作为字符串文字:
type MagicRecord<XField extends keyof FieldLookup<any>> = {
[K in keyof MyRecord]: FieldLookup<MyRecord[K]>[XField]
};
type FooRecord = MagicRecord<"FooField">;
type BarRecord = MagicRecord<"BarField">;
这样就可以了。我们可以更进一步,通过对 FieldLookup
:
实施反向查找,使您无需使用或关心类型名称
type ReverseLookup<F extends Field<any>> = {
[K in keyof FieldLookup<any>]: FieldLookup<any>[K] extends F ? K : never
}[keyof FieldLookup<any>];
然后 MagicRecord<XField>
将用作您的原始版本,除了 XField
必须是具体类型:
type MagicRecord2<XField extends Field<any>> = MagicRecord<
ReverseLookup<XField>
>;
type FooRecord2 = MagicRecord2<FooField<any>>; // FooField<any>, not FooField
type BarRecord2 = MagicRecord2<BarField<any>>; // BarField<any>, not BarField
这也行。
好的,希望对您有所帮助。祝你好运!
抱歉标题混乱,因为我不知道如何表达清楚。
我正在尝试实现一种创建泛型类型的方法,稍后将使用它来传递泛型类型。
为了消除混淆,给定这两个接口:
interface Field<T> {
value: T;
hasError: boolean;
}
interface Record {
id: number;
name: string;
}
以及Field
interface FooField<T> extends Field<T> {
foo: () => void;
}
interface BarField<T> extends Field<T> {
bar: () => void;
}
我想生成以下类型的 Record
的新类型:
interface FooRecord {
id: FooField<number>;
name: FooField<string>;
}
interface BarRecord {
id: BarField<number>;
name: BarField<string>;
}
最直观但不正确的方法如下:
type MagicRecord<XField extends Field> = {
[K in keyof Record]: XField<Record[K]>;
};
type FooRecord = MagicRecord<FooField>;
type BarRecord = MagicRecord<BarField>;
我怎样才能做到这一点,如果可能的话,正确地使用打字稿?
你想要 higher kinded types,从 v3.5 开始,TypeScript 不支持它。您在这里所做的任何事情都将是一种解决方法,涉及一定数量的代码重复。
这是一种可能的解决方法。首先,请注意,我不会使用名称 Record
,因为它与 built-in type alias 的名称冲突。我会切换到 MyRecord
:
interface MyRecord {
id: number;
name: string;
}
所以让我们查找 table FieldLookup<T>
,它从通用类型的名称映射到用 T
指定的通用类型本身。此 table 需要为您要支持的 Field<T>
的每个子类型创建一个条目:
interface FieldLookup<T> extends Record<keyof FieldLookup<any>, Field<T>> {
Field: Field<T>;
FooField: FooField<T>;
BarField: BarField<T>;
}
然后我们可以定义MagicRecord<XField>
,它将通用字段的名称作为字符串文字:
type MagicRecord<XField extends keyof FieldLookup<any>> = {
[K in keyof MyRecord]: FieldLookup<MyRecord[K]>[XField]
};
type FooRecord = MagicRecord<"FooField">;
type BarRecord = MagicRecord<"BarField">;
这样就可以了。我们可以更进一步,通过对 FieldLookup
:
type ReverseLookup<F extends Field<any>> = {
[K in keyof FieldLookup<any>]: FieldLookup<any>[K] extends F ? K : never
}[keyof FieldLookup<any>];
然后 MagicRecord<XField>
将用作您的原始版本,除了 XField
必须是具体类型:
type MagicRecord2<XField extends Field<any>> = MagicRecord<
ReverseLookup<XField>
>;
type FooRecord2 = MagicRecord2<FooField<any>>; // FooField<any>, not FooField
type BarRecord2 = MagicRecord2<BarField<any>>; // BarField<any>, not BarField
这也行。
好的,希望对您有所帮助。祝你好运!