如何在TypeScript中定义泛型参数可变的递归类型列表?
TypeScript 可变泛型类型定义问题
问题背景
给定如下类型定义:
type AThingWithGenerics<A, B, C> = { name: string; someFunction: (id: number) => A; anotherFunction: () => B; loveSomeGenerics: (s: string) => { foo: C }; };
其中A、B、C是随不同实例变化的对象类型,示例如下:
const aThing: AThingWithGenerics<{ foo: string }, { foo: number, bar: string }, { bar: string }> = { name: "a_thing", someFunction: (id: number) => { return { foo: "bar" }; }, anotherFunction: () => { return { foo: 3, bar: "foo" } }, loveSomeGenerics: (s: string) => { return { foo: { bar: "In this case C is an object containing a string" + s } }; }, } const anotherThing: AThingWithGenerics<{ bar: number }, { aString: string }, { bar: "test" }> = { name: "another_thing", someFunction: (id: number) => { return { bar: id + 1 }; }, anotherFunction: () => { return { aString: "test" } }, loveSomeGenerics: (s: string) => { return { foo: { bar: "test" } } }, }
可以创建由特定AThingWithGenerics实例组成的元组:
type ASpecificPileOfThings = [typeof aThing, typeof anotherThing];
也可以在A、B、C固定的前提下创建同类型列表:
type APileOfTheSameThing<A, B, C> = [AThingWithGenerics<A, B, C>];
需求与遇到的问题
需要为AThingWithGenerics新增related字段,存储多个相关的AThingWithGenerics实例,比如aThing的related可以包含[anotherThing],这就要求related数组中元素的A、B、C泛型参数是可变的。
原有实现如下,无法通过类型校验,因为不同实例的A、B、C类型无法统一:
type AThingWithGenericsII<A, B, C> = { name: string; someFunction: (id: number) => A; anotherFunction: () => B; loveSomeGenerics: (s: string) => { foo: C }; related: Array<AThingWithGenericsII<A,B,C>> } const anotherThingWithRelated: AThingWithGenericsII<{ bar: number }, { aString: string }, { bar: "test" }> = { name: "another_thing", someFunction: (id: number) => { return { bar: id + 1 } }, anotherFunction: () => { return { aString: "test" } }, loveSomeGenerics: (s: string) => { return { foo: { bar: "test" } } }, related: [] } const aThingWithRelated: AThingWithGenericsII<{foo: string, bar: string }, { property: string }, { nestedFoo: number }> = { name: "a_thing_with_related", someFunction: (id: number) => { return { foo: "This is a string", bar: "" } }, anotherFunction: () => { return { property: "bar" } }, loveSomeGenerics: (s: string) => { return { foo: { nestedFoo: 3.1 } } }, related: [anotherThingWithRelated] }
解决方案
原有代码报错的核心原因是related字段的类型强制要求所有关联实例的A/B/C泛型和当前实例完全相同,不符合存储不同泛型实例的需求。
你可以先定义一个无泛型约束的顶层类型,所有AThingWithGenerics实例都符合这个类型的结构,再把related字段的类型设置为这个顶层类型的数组即可:
// 无泛型的顶层类型,作为所有泛型变体的公共父类型 type AnyAThing = { name: string; someFunction: (id: number) => unknown; anotherFunction: () => unknown; loveSomeGenerics: (s: string) => { foo: unknown }; related: AnyAThing[]; }; // 泛型类型继承顶层类型,保留本身的泛型约束 type AThingWithGenerics<A, B, C> = AnyAThing & { someFunction: (id: number) => A; anotherFunction: () => B; loveSomeGenerics: (s: string) => { foo: C }; };
修改后你的示例代码就可以正常通过类型校验,同时每个实例本身的三个泛型约束依然生效,调用实例自身的方法时能拿到正确的返回类型。访问related数组内的元素时默认会拿到AnyAThing类型,如果需要用到具体类型可以手动做类型断言。
如果需要更精准的类型推导,你可以给泛型加第四个可选参数,显式指定related数组的元素类型:
type AThingWithGenerics<A, B, C, R extends AnyAThing = AnyAThing> = AnyAThing & { someFunction: (id: number) => A; anotherFunction: () => B; loveSomeGenerics: (s: string) => { foo: C }; related: R[]; };
定义实例时如果明确知道related里的元素类型,传入对应的第四个泛型参数即可获得更精确的类型提示。
内容的提问来源于stack exchange,提问作者user4301448
相关产品推荐
相关产品推荐

