gpt4 book ai didi

typescript - 定义一个基于递归泛型接口(interface)的 TypeScript 类

转载 作者:行者123 更新时间:2023-12-05 04:41:07 28 4
gpt4 key购买 nike

我正在为一个项目设置一个基础数据结构,希望有一个抽象的 GraphNode 基础对象,许多其他对象将从中继承。每个 GraphNode 子类都有元数据,可以包括文字(字符串、数字等)和对其他 GraphNode 派生类型的引用。

示例子类:

interface IPerson {
name: string;
age: number;
friend: Person;
pet: Pet;
}

interface IPet {
type: 'dog' | 'cat';
name: string;
}

class Person extends GraphNode<IPerson> {}
class Pet extends GraphNode<IPet> {}

示例用法:

const spot = new Pet()
.set('type', 'dog')
.set('name', 'Spot');

const jo = new Person()
.set('name', 'Jo')
.set('age', 41)
.set('pet', spot) // ⚠️ Should not accept GraphNode argument.
.setRef('pet', spot); // ✅ Correct.

const sam = new Person()
.set('name', 'Sam')
.set('age', 45)
.set('friend', jo) // ⚠️ Should not accept GraphNode argument.
.setRef('friend', jo); // ✅ Correct.

我的问题是——当 GraphNode 基类的泛型递归依赖于 GraphNode 类时,我该如何定义它?这是我的尝试:

实用程序类型:

type Literal = null | boolean | number | string;

type LiteralAttributes<Base> = {
[Key in keyof Base]: Base[Key] extends Literal ? Base[Key] : never;
};

// ⚠️ ERROR — Generic type 'GraphNode<Attributes>' requires 1 type argument(s).
type RefAttributes<Base> = {
[Key in keyof Base]: Base[Key] extends infer Child
? Child extends GraphNode | null
? Child | null
: never
: never;
};

抽象图定义:

type GraphAttributes = {[key: string]: Literal | GraphNode | null};

abstract class GraphNode<Attributes extends GraphAttributes> {
public attributes = {} as LiteralAttributes<Attributes> | RefAttributes<Attributes>;

public get<K extends keyof LiteralAttributes<Attributes>>(key: K): Attributes[K] {
return this.attributes[key]; // ⚠️ Missing type information.
}

public set<K extends keyof LiteralAttributes<Attributes>>(key: K, value: Attributes[K]): this {
this.attributes[key] = value;
return this;
}

public getRef<K extends keyof RefAttributes<Attributes>>(key: K): Attributes[K] | null {
return this.attributes[key]; // ⚠️ Missing type information.
}

public setRef<K extends keyof RefAttributes<Attributes>>(key: K, value: Attributes[K] | null): this {
this.attributes[key] = value; // ⚠️ Missing type information.
return this;
}
}

那不会编译,我已经在上面的评论中标记了 TS 错误。我认为这归结为一个基本问题——我想稍后扩展泛型类,使用引用泛型类的类型参数,但我不确定该怎么做。我可以忍受 GraphNode 类本身没有严格的类型检查,但我真的希望它的子类有严格的类型检查。

我还创建了一个 example TypeScript playground使用编译器错误重现此代码。

最佳答案

原始代码中存在很多问题,导致它无法按您希望的方式运行。首先,获取对象类型的键 T其值可分配给类型 V ,你可以这样做:

type KeysMatching<T, V> = {[K in keyof T]-?: T[K] extends V ? K : never}[keyof T];

maps来自 T 的属性键Knever depending on whether or not这些属性是否可分配给 V ,然后立即 indexes使用 keyof T 进入该映射类型得到union我们关心的 key 。

我们可以专门化它来获取 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]

所以我们可以使用LiteralKeys<A>而不是你的 keyof LiteralAttributes<A>这没有用,因为 keyof LiteralAttributes<A>始终与 keyof A 相同(您将错误的属性值映射到 never 但这并没有消除 key )。


现在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;
}
}

注意你GraphNode<A>generic在类型参数中 A , 每当你提到 GraphNode您必须指定泛型类型参数。你不能只说 GraphNode .如果您不知道或不关心类型参数应该是什么,您可以使用 the any type , 比如 GraphNode<any> .这将始终有效,但最终可能会允许一些您不想允许的事情。在这种情况下,它可能没问题。

AconstrainedRecord<keyof A, Literal | GraphNode<any>>而不是 {[k: string]: Literal | GraphNode<any>}因为我们真的不想要求 A有一个string index signature .通过约束 ARecord<keyof A, ...>我们是说 key 可以是任何它们碰巧是的东西。

attributes属性类型为 Partial<A> .这是A因为我们想持有类似 A 的东西而不是像LiteralAttributes<A> | RefAttributes<A>这样的东西.我们正在使用 Partial<T> utility type承认在任何给定时间它可能不具有 A 的所有属性放;实际上它被初始化为 {} ,它根本没有任何属性。这也意味着我已经更改了 get()getRef()返回类型包括 undefined .

get()set() K extends LiteralKeys<A> 中的方法是通用的, 而 getRef()setRef() K extends RefKeys<A> 中的方法是通用的.最合理A类型,LiteralKeys<A>RefKeys<A>将互斥并共同构成所有keyof T .如果A具有本身是 Literal 的并集或交集的任何属性和 GraphNode<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);

所以看起来不错!

Playground link to code

关于typescript - 定义一个基于递归泛型接口(interface)的 TypeScript 类,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70146585/

28 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com