我在掙扎。
當擴展一個類時,我可以輕松地向它添加一些新屬性。
但是,如果在擴展基類時,我想向基類的物件(一個簡單物件的屬性)添加新屬性,該怎么辦?
這是一個帶有一些代碼的示例。
基類
type HumanOptions = {
alive: boolean
age: number
}
class Human {
id: string
options: HumanOptions
constructor() {
this.id = '_' Math.random()
this.options = {
alive: true,
age: 25,
}
}
}
派生類
type WizardOptions = {
hat: string
} & HumanOptions
class Wizard extends Human{
manaLevel: number
options: WizardOptions // ! Property 'options' has no initializer and is not definitely assigned in the constructor.
constructor() {
super()
this.manaLevel = 100
this.options.hat = "pointy" // ! Property 'options' is used before being assigned.
}
}
現在,正如您從內嵌注釋中看到的那樣,這會激怒 TypeScript。但這適用于 JavaScript。那么實作這一目標的正確方法是什么?如果沒有,是我的代碼模式本身有問題嗎?什么樣的模式適合這個問題?
也許你想知道為什么我想要一個options物件中的這些屬性。它可能非常有用!例如:只有 中的屬性options通過某些 UI 進行編輯。因此,其他一些代碼可以options在類中動態查找并在 UI 上公開/更新它們,同時像id和manaLevel這樣的屬性將被保留。
筆記
我知道我可以為向導做到這一點:
class Wizard extends Human{
manaLevel: number
options: {
hat: string
alive: boolean
age: number
}
constructor() {
super()
this.manaLevel = 100
this.options = {
alive: true,
age: 25,
hat: "pointy"
}
}
}
它有效。但是代碼不是很干,我必須手動管理選項繼承(這在復雜的應用程式中并不理想)。
uj5u.com熱心網友回復:
如果您只是要重用超類中的屬性,但將其視為更窄的型別,則您可能應該在子類中使用declare屬性修飾符,而不是重新宣告該欄位:
class Wizard extends Human {
manaLevel: number
declare options: WizardOptions; // <-- declare
constructor() {
super()
this.manaLevel = 100
this.options.hat = "pointy" // <-- no problem now
}
}
通過declare在此處使用,您可以抑制該行的任何 JavaScript 輸出。它所做的只是告訴 TypeScript 編譯器在 中Wizard,該options屬性將被視為 aWizardOptions而不僅僅是 a HumanOptions。
公共類欄位目前處于TC39 行程的第 3 階段,因此很可能以其當前形式或接近于它的形式成為 JavaScript 的一部分。當這種情況發生時(或者現在,如果你有一個足夠新的 JS 引擎),你撰寫的代碼最終會生成看起來像的 JavaScript
class Wizard extends Human {
manaLevel;
options; // <-- this will be here in JavaScript
constructor() {
super();
this.manaLevel = 100;
this.options.hat = "pointy";
}
}
這只是沒有型別注釋的 TypeScript。 但是optionsJavaScript 中的該宣告將options在子類中初始化為undefined. 因此,this.options在分配之前使用它確實是正確的:
console.log(new Wizard()); // runtime error!
// this.options is undefined
所以你從 TypeScript 得到的錯誤是一個很好的錯誤。
最初 TypeScript 實作了類欄位,因此如果您沒有顯式初始化一個欄位,那么僅宣告一個欄位就不會產生影響。這就是 TypeScript 團隊認為類欄位最終會在 JavaScript 中實作的方式。但他們錯了。如果您的編譯器選項未啟用--useDefineForClassFields(并注意,如果您制作--target足夠新的內容,它將被自動包含;請參閱microsoft/TypeScript#45653),那么它將輸出 JS 代碼,其中您的示例沒有發生運行時錯誤。
因此,您可能會爭辯說,如果您不使用--useDefineForClassFields. 在--useDefineForClassFields存在之前,這樣的論點實際上是在microsoft/TypeScript#20911和microsoft/TypeScript#21175 中提出的。
但我認為 TS 團隊不再愿意接受這一點了。隨著時間的推移,在 JavaScript 中宣告的類欄位被初始化undefined將成為規范而不是例外,并且支持舊的不符合版本的類欄位不是任何人的優先事項。請參閱microsoft/TypeScript#35831,其中建議基本上是“使用declare”。這也是我在這里建議的。
Playground 鏈接到代碼
uj5u.com熱心網友回復:
這是部分答案,我會在您提供更多資訊時更新。例如,對上述問題下問題的回答。
你的第二個錯誤是你在宣告選項欄位結果Wizard 躲在 你宣告的選項欄位Human。這就是大多數(所有)基于類的型別系統的作業方式,而不僅僅是 TS。因此Wizard.options在this.options.hat = "pointy"執行時未定義。你可以通過評論看到這一點options: WizardOptions
這在 Javascript 中有效,因為您依賴于原型繼承,并且沒有options在Wizard類中宣告一個新欄位(Human.options如果您有,它也會被隱藏。
uj5u.com熱心網友回復:
您可以通過以下方式進行:操場
type HumanOptions = {
alive: boolean
age: number
}
class Human<Options extends HumanOptions> {
id: string
options: Options
constructor() {
this.id = '_' Math.random()
this.options = {
alive: true,
age: 25,
} as Options
}
}
type WizardOptions = {
hat: string
} & HumanOptions
class Wizard extends Human<WizardOptions> {
manaLevel: number
constructor() {
super()
this.manaLevel = 100
this.options.hat = "pointy"
}
}
請注意,您全權負責設定options.
轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/374662.html
下一篇:Linux: 多執行緒
