我想創建一個相當靈活的類,名為Model,如:
export class Model {
_required_fields: Array<string> = [];
_optional_fields?: Array<string> = [];
constructor(params: Dictionary<string> = {}) {
// make sure all required fields are in the params obj
}
set(params: Dictionary<string>){
// make sure only required or optional fields are present
this.all_fields.forEach(key => {
this[key] = params[key];
});
}
get all_fields(): Array<string> {
return [...this._required_fields,...this._optional_fields];
}
get required_fields() {
return this._required_fields;
}
}
這個函數的子函數將定義必填字段和可選字段,我將其縮短,因為我在set
方法中進行了一些錯誤檢查。例如:
export class User extends Model {
static REQUIRED_FIELDS = ['username'];
static OPTIONAL_FIELDS = ['email'];
get required_fields() {
return (this._required_fields?.length==0) ? User.REQUIRED_FIELDS : [];
}
static get ALL_FIELDS() {
return [...User.REQUIRED_FIELDS, ...User.OPTIONAL_FIELDS];
}
constructor(params: Dictionary<string> = {}) {
super(params);
}
}
我有一個User
版本,其中包含以下字段:
username: string;
email: string;
但是我希望能夠定義字段,以便set
函數可以接受一個Dictionary
并填充字段,如圖所示。
我在以下行中得到了類型腳本錯誤No index signature with a parameter of type 'string' was found on type 'Model'.
:
this[key] = params[key];
我意識到這一點,因為我需要在Model
的內部定義一個類似:[key: string]: string;
的字段。
這似乎有兩種可能性:
方法1:在User
的內部,定義每個字段,并在set
(ofUser
)的內部顯式執行
user.username = params.username;
user.email = params.email;
然而,我必須對模型的所有子級重復這一點,我有一些錯誤檢查,我想自動化一點。
方法2:或者,我可以保持模型具有泛型字段
[key: string]: string;
然后set
將按原樣工作,但沒有能力執行user.username
,但可以執行user['username']
。
summary
到目前為止,我已經完成了方法1,并且有大量重復的代碼,因為我需要為Model
的每個子級顯式地完成所有字段。(實際上,我有兩個以上的字段)。這并不令人滿意,似乎我可以在Model
類中編寫更緊湊的內容,而不是每個孩子。
方法2似乎繞過了typescript的許多強類型,因此,盡管代碼更緊湊,但似乎并不太好。
Question
我有沒有辦法將typescript的強類型與方法1的靈活性結合起來
看起來您希望
Model
保持跟蹤requiredFields
和optionalFields
元素中的實際文字值,這意味著Model
在這些類型中可能是泛型的。所以可能類似于Model<R, O>
,其中R
是所有必需鍵的并集,O
是所有可選鍵的并集。但是您還希望
Model<R, O>
實際具有R
和O
類型的鍵。這就是我們在使用常規的class
語句時遇到麻煩的地方。類和接口要求編譯器靜態地知道它們的鍵。它們不能是動態的,不能在以后填寫:所以我們需要解決這個問題。
描述這樣的類構造函數類型很容易。應該是這樣的
因此,
ModelConstructor
有一個構造簽名,它接受類型為ModelObject<R, O>
的params
。ModelObject<R, O>
是一種對象類型,具有必需的鍵R
和可選鍵O
,其值均為string
。構造的實例也是一個ModelObject<R, O>
,它與Model
類中的一組適當類型的屬性和方法相交。不過,在繼續討論這個問題之前,考慮一下如何將
Model
子類化可能會很有用。由于您希望User
的每個實例都具有相同的必填字段和可選字段,因此將Model
設置為類工廠函數而不是class
構造函數本身可能是有意義的。否則,您需要冗余地指定R
和O
:寫下來可能會更好
其中
Model(["username"], ["email"])
生成一個類構造函數,其中這些字段已經存在。這取決于你是否想這樣做。我將假設工廠函數是可以接受的,并繼續使用它。這里是工廠函數:
注意,從
Model
返回的類構造函數關閉傳遞給它的requiredFields
和optionalFields
變量。這些類型與以前類似,只是
R
和O
現在是工廠函數上的泛型類型參數,而不是結果類。作用域略有不同,因此(例如)ModelObject
不需要在R
和O
中是泛型的,因為R
和O
在該作用域中是已知的。還有一些拋出的extends infer T ? {[K in keyof T]: T[K]}: never
類型,它們只是要求編譯器將類似Record<"username", string> & Partial<Record<"email", string>>
的東西擴展成一個更好看的{username: string; email?: string}
類型。還要注意,由于編譯器不能將
class
表示為具有動態屬性,因此我需要使用類型斷言來告訴編譯器它可以將返回的構造函數視為正確的類型。一旦使用工廠函數,您就可以決定進一步重構;也許您希望必需/可選字段僅為返回的類構造函數的
static
屬性,因為它們對于類的每個實例都應該相同。不過我不想麻煩了。只是一個想法。讓我們測試一下。首先讓我們看看當我們調用
Model
時會出現什么結果:看起來很合理,我想你想要什么。現在我們可以定義
User
:我注釋掉的那些
static
屬性來自您的示例,但它們不是我的解決方案所必需的。如果你想要它們,可以隨意添加,但我不知道它們有什么用途。讓我們測試一下:
Looks good!
操場連結至守則