Don*_*rdy 5 generics typescript typescript-generics
我正在为一个项目设置一个基础数据结构,希望有一个抽象的基础GraphNode对象,许多其他对象将从中继承。每个 GraphNode 子类都具有元数据,其中可能包括文字(字符串、数字等)和对其他 GraphNode 派生类型的引用。
子类示例:
\ninterface IPerson {\n name: string;\n age: number;\n friend: Person;\n pet: Pet;\n}\n\ninterface IPet {\n type: \'dog\' | \'cat\';\n name: string;\n}\n\nclass Person extends GraphNode<IPerson> {}\nclass Pet extends GraphNode<IPet> {}\nRun Code Online (Sandbox Code Playgroud)\n用法示例:
\nconst spot = new Pet()\n .set(\'type\', \'dog\')\n .set(\'name\', \'Spot\');\n\nconst jo = new Person()\n .set(\'name\', \'Jo\')\n .set(\'age\', 41)\n .set(\'pet\', spot) // \xe2\x9a\xa0\xef\xb8\x8f Should not accept GraphNode argument.\n .setRef(\'pet\', spot); // \xe2\x9c\x85 Correct.\n\nconst sam = new Person()\n .set(\'name\', \'Sam\')\n .set(\'age\', 45)\n .set(\'friend\', jo) // \xe2\x9a\xa0\xef\xb8\x8f Should not accept GraphNode argument.\n .setRef(\'friend\', jo); // \xe2\x9c\x85 Correct.\nRun Code Online (Sandbox Code Playgroud)\n我的问题是 \xe2\x80\x94 当它的泛型类型递归地依赖于 GraphNode 类时,如何定义 GraphNode 基类?这是我的尝试:
\n实用程序类型:
\ntype Literal = null | boolean | number | string;\n\ntype LiteralAttributes<Base> = {\n [Key in keyof Base]: Base[Key] extends Literal ? Base[Key] : never;\n};\n\n// \xe2\x9a\xa0\xef\xb8\x8f ERROR \xe2\x80\x94 Generic type \'GraphNode<Attributes>\' requires 1 type argument(s).\ntype RefAttributes<Base> = {\n [Key in keyof Base]: Base[Key] extends infer Child\n ? Child extends GraphNode | null\n ? Child | null\n : never\n : never;\n};\nRun Code Online (Sandbox Code Playgroud)\n抽象图定义:
\ntype GraphAttributes = {[key: string]: Literal | GraphNode | null};\n\nabstract class GraphNode<Attributes extends GraphAttributes> {\n public attributes = {} as LiteralAttributes<Attributes> | RefAttributes<Attributes>;\n\n public get<K extends keyof LiteralAttributes<Attributes>>(key: K): Attributes[K] {\n return this.attributes[key]; // \xe2\x9a\xa0\xef\xb8\x8f Missing type information.\n }\n\n public set<K extends keyof LiteralAttributes<Attributes>>(key: K, value: Attributes[K]): this {\n this.attributes[key] = value;\n return this;\n }\n\n public getRef<K extends keyof RefAttributes<Attributes>>(key: K): Attributes[K] | null {\n return this.attributes[key]; // \xe2\x9a\xa0\xef\xb8\x8f Missing type information.\n }\n\n public setRef<K extends keyof RefAttributes<Attributes>>(key: K, value: Attributes[K] | null): this {\n this.attributes[key] = value; // \xe2\x9a\xa0\xef\xb8\x8f Missing type information.\n return this;\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n这无法编译,我已在上面的注释中标记了 TS 错误。我认为这归结为一个基本问题 \xe2\x80\x93 我想稍后使用引用泛型类的类型参数来扩展泛型类,但我不知道该怎么做。我可以忍受 GraphNode 类本身没有严格的类型检查,但我确实希望它的子类有严格的类型检查。
\n我还创建了一个示例 TypeScript Playground来重现此带有编译器错误的代码。
\n原始代码中存在很多问题,导致其行为不符合您想要的方式。T首先,要获取其值可分配给 type 的对象类型的键V,您可以执行以下操作:
type KeysMatching<T, V> = {[K in keyof T]-?: T[K] extends V ? K : never}[keyof T];
Run Code Online (Sandbox Code Playgroud)
这会将属性 from映射T到键K或 ,never 具体取决于这些属性是否可分配给V,然后立即索引到该映射类型keyof T以获取我们关心的键的并集。
我们可以专门获取T其属性可分配给其属性的键Literal以及其属性可分配给某种GraphNode<X>类型的键:
type LiteralKeys<T> = { [K in keyof T]-?: T[K] extends Literal ? K : never }[keyof T];
type RefKeys<T> = { [K in keyof T]-?: T[K] extends GraphNode<any> ? K : never }[keyof T]
Run Code Online (Sandbox Code Playgroud)
因此,我们可以使用whichLiteralKeys<A>来代替你的keyof LiteralAttributes<A>which 不起作用,因为keyof LiteralAttributes<A>它始终与keyof A(你将错误的属性值映射到never,但这并没有消除该键)相同。
现在GraphNode可以这样定义:
abstract class GraphNode<A extends Record<keyof A, Literal | GraphNode<any>>> {
public attributes: Partial<A> = {};
public get<K extends LiteralKeys<A>>(key: K): A[K] | undefined {
return this.attributes[key];
}
public set<K extends LiteralKeys<A>>(key: K, value: A[K]): this {
this.attributes[key] = value;
return this;
}
public getRef<K extends RefKeys<A>>(key: K): A[K] | undefined {
return this.attributes[key];
}
public setRef<K extends RefKeys<A>>(key: K, value: A[K] | undefined): this {
this.attributes[key] = value;
return this;
}
}
Run Code Online (Sandbox Code Playgroud)
请注意,您在类型参数中GraphNode<A>是泛型的A,并且无论何时引用GraphNode都必须指定泛型类型参数。你不能只是说GraphNode。如果您不知道或不关心类型参数应该是什么,您可以使用type any,例如GraphNode<any>。这总是有效的,但最终可能会允许一些你不想允许的事情。在这种情况下,可能没问题。
A被限制为Record<keyof A, Literal | GraphNode<any>>而不是{[k: string]: Literal | GraphNode<any>}因为我们真的不想要求A有一个string 索引签名。通过限制A我们Record<keyof A, ...>是说键可以是任何它们碰巧是什么。
该attributes属性的类型为Partial<A>。这是A因为我们想要持有非常相似的东西A,而不是类似的东西LiteralAttributes<A> | RefAttributes<A>。我们使用Partial<T>实用程序类型A来确认在任何给定时间它可能不具有set的所有属性;事实上它被初始化为{},它根本没有属性。这也意味着我已经更改了get()和getRef()返回类型以包含undefined.
和方法在 中通用get(),而和方法在 中通用。对于大多数合理的类型,和是互斥的,并且一起组成 的全部。如果有任何属性本身是 的并集或交集,那么这可能不是真的。我在这里并不担心这一点,但如果确实发生这种情况,您可能会遇到一些奇怪的边缘情况。set()K extends LiteralKeys<A>getRef()setRef()K extends RefKeys<A>ALiteralKeys<A>RefKeys<A>keyof TALiteralGraphNode<any>
让我们确保它的行为符合您的要求。您的子类和用法按预期编译,甚至导致以下结果:
const jo = new Person()
.set('name', 'Jo')
.set('age', 41)
.set('pet', spot) // error! Argument of type '"pet"'
// is not assignable to parameter of type 'LiteralKeys<IPerson>'
.setRef('pet', spot);
const sam = new Person()
.set('name', 'Sam')
.set('age', 45)
.set('friend', jo) // error! Argument of type '"friend"'
// is not assignable to parameter of type 'LiteralKeys<IPerson>'
.setRef('friend', jo);
Run Code Online (Sandbox Code Playgroud)
所以看起来不错!
| 归档时间: |
|
| 查看次数: |
1263 次 |
| 最近记录: |