我正在為一個專案設定一個基礎資料結構,希望有一個抽象的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;
}
}
That doesn't compile, and I've flagged the TS errors in comments above. I think that comes down to a basic problem – I want to extend the generic class later, with type arguments that refer to the generic class, and I'm not sure how to do that. I could live with not having strict type checking within the GraphNode class itself, but I really do want its subclasses to have strict type checking.
I've also created an example TypeScript playground reproducing this code with the compiler errors.
uj5u.com熱心網友回復:
原始代碼中存在很多問題,使其無法按照您想要的方式運行。首先,要獲取T其值可分配給 type的物件型別的鍵V,您可以執行以下操作:
type KeysMatching<T, V> = {[K in keyof T]-?: T[K] extends V ? K : never}[keyof T];
這將屬性 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]
因此,我們可以使用LiteralKeys<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;
}
}
請注意,您在型別引數中GraphNode<A>是泛型的A,無論何時參考,GraphNode您都必須指定泛型型別引數。你不能只說GraphNode。如果你不知道或不關心的型別引數應該是什么,你可以使用的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>實用程式型別來確認在任何給定時間它可能不具有Aset 的所有屬性;實際上它被初始化為{},它根本沒有任何屬性。這也意味著我已將get()和getRef()回傳型別更改為 include 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 鏈接到代碼
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/370268.html
標籤:typescript generics typescript-generics
上一篇:轉換為任意版本的泛型類
