我需要基于受约束的泛型创建新类型。新类型应该与泛型具有相同的键,使它们成为可选的,并将它们映射到一个数字。
我的第一个方法是使用Partial<Record<keyof Entity, number>>,但由于某种原因导致错误Type [...] is not assignable to type 'Partial<Record<keyof Entity, number>>'.。
所以我求助于手动编写类型映射{ [K in keyof Entity]?: number },这似乎很管用。
interface Person { firstName: string; lastName: string; } export class SomeClass<Entity extends Person> { protected getFilter(): void { let prio1: { [K in keyof Entity]?: number }; prio1 = { firstName: 1 }; // Type '{ firstName: 2; }' is not assignable to type 'Partial<Record<keyof Entity, number>>'. let prio2: Partial<Record<keyof Entity, number>>; prio2 = { firstName: 2 }; } }谁能告诉我这两种表示法之间的区别是什么,为什么内置的打字实用程序不能工作?
该代码片段也可在TypeScrip Platform上找到:see here。
我使用的资源:Records、Mapped Types、Generics
更新:
我试图分解TypeScrip在内部的作用,使用官方的类型定义一步一步地对其进行美化:
/** * Make all properties in T optional */ type Partial<T> = { [P in keyof T]?: T[P] }; /** * Construct a type with a set of properties K of type T */ type Record<K extends keyof any, T> = { [P in K]: T }; class SomeClass<Entity extends Person> { getFilter(): void { let prio1: { [K in keyof Entity]?: number }; prio1 = { firstName: 1 }; let p1: Partial<Record<keyof Entity, number>>; let p2: Partial<{ [P in keyof Entity]: number }>; let p3: { [P2 in keyof { [P1 in keyof Entity]: number }]?: { [P1 in keyof Entity]: number }[P2] }; let p4: { [P2 in keyof Entity]?: { [P1 in keyof Entity]: number }[P2] }; let p5: { [P2 in keyof Entity]?: number }; p1 = { firstName: 3 }; // error p2 = { firstName: 3 }; // error p3 = { firstName: 3 }; // error p4 = { firstName: 3 }; // error p5 = { firstName: 3 }; // works } }TypeScrip似乎无法将{ [P1 in keyof Entity]: number }[P2]与P2 in keyof Entity解析为number。
推荐答案首先请看这个answer。它应该会为您提供一些背景信息。
将Entity视为Person的子类型。它不仅有Person道具,还可能有其他道具。
请参见此示例:
interface Person { firstName: string; lastName: string; } type User = { premium: true } & PersonUser是Person的子类型,也就是扩展了Person。
让我们创建用户对象:
interface Person { firstName: string; lastName: string; } type User = { premium: true } & Person let user: Record<keyof User, number> = { firstName: 1, lastName: 2, premium: 3 }因此,我们的Partial用户可能有很多状态:
// just an alias type PartiaUser = Partial<Record<keyof User, number>> let partialUser1: PartiaUser = {} let partialUser2: PartiaUser = { firstName: 1 } let partialUser3: PartiaUser = { firstName: 1, lastName: 2, } let partialUser4: PartiaUser = { lastName: 2, } let partialUser5: PartiaUser = { lastName: 2, premium: 3, }参见partialUser5它是一个没有firstName属性的对象。
让我们回到您的示例:
interface Person { firstName: string; lastName: string; } export class SomeClass<Entity extends Person> { getFilter(): void { let prio1: { [K in keyof Entity]?: number }; prio1 = { firstName: 1 }; let prio2: Partial<Record<keyof Entity, number>>; prio2 = { firstName: 2 }; } }prior1之所以有效,是因为在这种情况下,prior1肯定具有可选的firstName属性,因为Entity具有Person的所有键。
prior2非常有趣。
我认为问题出在Java脚本的动态性上。这只是我的观点。
请参见此示例:
type User = { firstName: string; lastName: string; premium: true } & string type IsExtends<T> = T extends Person ? true : false type Test = IsExtends<User>问题是上述User类型仍然扩展Person。
interface Person { firstName: string; lastName: string; } type User = { firstName: string; lastName: string; premium: true } & string export class SomeClass<Entity extends Person> { getFilter(): void { let prio1: { [K in keyof Entity]?: number }; prio1 = { firstName: 1 }; let prio2: Partial<Record<keyof Entity, number>>; prio2 = { firstName: 2 }; } } const result = new SomeClass<User>() // no error事实上,User是一个带有一些静态属性的字符串。
它在类型系统中可表示,但在运行时不可表示。
让我们用一个小例子进行测试:
type PartialUser = Partial<Record<keyof User, number>> declare var partialUser: PartialUser partialUser = { firstName: 2 } // error 在这种情况下,问题出在toString方法中。请看一下toString是如何在string原语中实现的: // toString: () => string; type ToString = Pick<{ [Prop in keyof string]: string[Prop] }, 'toString'>您可能已经注意到,toString是一个函数。我的意思是toString的值是一个函数。但是,Record<keyof User, number>预期每个值都是number。
让我们再来看看我们的示例:
interface Person { firstName: string; lastName: string; } type User = { firstName: string; lastName: string; premium: true; toString: number; } export class SomeClass<Entity extends Person> { getFilter(): void { let prio1: { [K in keyof Entity]?: number }; prio1 = { firstName: 1 }; // added User for experimenting with prio2 = { firstName: 2, toString: 42 } let prio2: Partial<Record<keyof User, number>>; prio2 = { firstName: 2 }; } } const result = new SomeClass<User>() // no error为清楚起见,我已显式添加了toString属性。
prio1 = { firstName: 1 }-之所以有效,是因为我们创建了新类型,其中每个值都是number。
由于我们已覆盖内置方法,因此TypeScrip声明此方法与默认方法不兼容toString。
尝试使用prio2 = { firstName: 2, toString: 42 }。您将看到它将修复错误。默认情况下,TS期望每个对象都有toString作为方法,这是预期的行为。但如果您覆盖它-它会使TS不高兴
我希望现在清楚您为什么会出错。
extends-不表示-equal
Playground
Related github issue
更多推荐
类型脚本实用程序记录和部分不能与受约束的泛型一起使用
发布评论