我正在尋找一種在我擁有的類上動態鍵入一些值和方法的方法。
例子
我會從簡單的開始。這是我想要的行為。(我認為)
const options = ["opt1", "opt2"] as const;
type AdvancedFixedValue = {
[L in typeof options[number]]: number;
};
這讓我知道我是否設定了不存在的屬性。
const advancedExample: AdvancedFixedValue = {
opt1: 2,
opt2: 3,
opt3: 3 // This will error
};
問題
然而,選項不是一個定義的串列,而是半動態的(它們不是來自資料庫或任何東西)。這是我的完整實作。
活動課
在活動類上,我想添加“固定值”,它只是一個物件,但希望根據方程式類上的變數值檢查該物件的鍵。
type BasicFixedValue = {
[key: string]: number;
};
class Activity {
id: string;
equation: Equation;
fixedValues: BasicFixedValue;
constructor() {
this.id = "test";
this.fixedValues = {};
this.equation = new Equation({ variables: ["test"] });
}
public setEquation(equation: Equation): Activity {
this.equation = equation;
return this;
}
public addFixedValues(fixedValues: BasicFixedValue): Activity {
this.fixedValues = {
...this.fixedValues,
...fixedValues
};
return this;
}
}
方程類
type EquationParams = {
variables?: string[];
};
export class Equation {
id: string;
equation?: string;
variables: string[] = [""];
constructor(params: EquationParams) {
this.id = "test";
this.variables = params?.variables ?? ["hi"];
}
public setEquation(equation: string): Equation {
this.equation = equation;
return this;
}
public addVariable(variables: string[] | string): Equation {
const deDupe = new Set(this.variables);
if (variables instanceof Array) {
variables.forEach((variable) => deDupe.add(variable));
} else {
deDupe.add(variables);
}
this.variables = Array.from(deDupe);
return this;
}
}
測驗用例
// Make a new equation with "a" and "b"
const equation1 = new Equation({ variables: ["a", "b"] })
// Add it to the activity
const activity1 = new Activity().setEquation(equation1);
// This should show a typescript error because "test" is not in the variables of the equation added to the activity
activity1.addFixedValues({ c: 20 });
// This should be fine
activity1.addFixedValues({ a: 20 });
// After adding "c" to the equation,
equation1.addVariable("c");
// it should now be fine
activity1.addFixedValues({ c: 20 });
uj5u.com熱心網友回復:
TypeScript 并不能很好地跟蹤型別突變;該語言的默認立場是運算式的型別表示運算式可以具有的可能值,并且它不會隨著時間而改變。所以一個變數let x: string = "foo"永遠不能保存一個number值,如果你認為你可能想要它,你應該像這樣注釋它let x: string | number = "foo"。
嗯,有型別縮小的概念,編譯器將采用一個型別的變數,X并Y extends X根據控制流分析暫時將其視為某個子型別。所以let x: string | number = "foo"會導致編譯器看到x縮小為string,并且可以x.toUppercase()無誤地撰寫。如果您重新分配x = 4編譯器將重新擴大到string | number然后重新縮小到number這樣您就可以毫無錯誤地撰寫x.toFixed(2):
let x: string | number = "foo";
x.toUpperCase(); // okay
x.toFixed // error
x = 5; // okay
x.toFixed(); // okay
x.toUpperCase // error
當我第一次看到你的問題時,我有一些模糊的希望,也許我們可以重構你的代碼,以便它將你正在做的事情視為這種基于范圍的型別縮小。甚至還有一些稱為斷言函式/方法的功能,您可以在其中將void-returning 類方法注釋為回傳形式where的斷言謂詞,并且編譯器知道呼叫該函式將縮小類實體的型別。asserts this is YY extends this
但是在實踐中很難使用斷言方法,因為它們需要在您不會想到使用它們的地方進行顯式型別注釋(請參閱microsoft/TypeScript#45385以獲取解除此條件的功能請求),并且沒有簡單的方法使用它們來重新加寬縮小的型別。在這種特定情況下,我無法讓編譯器將呼叫setEquation()an的操作Activity視為縮小(由于編譯器測量方差的方式變幻莫測),因此將其稱為斷言方法無效。
關鍵是,這種事情很難做到。
因此,與其嘗試將其作為一個mutation來做,我們可以將其重構為讓操作產生新型別的結果,如果您需要多次參考它們,您將這些結果存盤在新變數中。這就像使用流暢的介面,您可以將方法鏈接在一起。這里的規則是,如果您對變數執行了會改變其狀態的操作,則永遠不要重復使用該變數。(您可以通過使所有內容不可變來防止這種情況發生,因此activity.setEquation()會回傳一個全新的Activity而不是改變現有的,但我現在不會擔心這個)。
例如:
type EquationParams<K extends string> = {
variables: K[];
};
class Equation<K extends string> {
id: string;
equation?: string;
variables: K[] = [];
constructor(params: EquationParams<K>) {
this.id = "test";
this.variables = params.variables;
}
public addVariable<L extends string>(variables: L[] | L): Equation<K | L> {
const deDupe = new Set<K | L>(this.variables);
if (variables instanceof Array) {
variables.forEach((variable) => deDupe.add(variable));
} else {
deDupe.add(variables);
}
const that = this as Equation<K | L>;
that.variables = Array.from(deDupe);
return that;
}
}
在這里,Equation現在泛型在字串字面量型別的元素中是它的variables屬性。如果有一個Equation<K>與呼叫addVariable(v),其中v是式的L,編譯器將回傳一個Equation<K | L>,使用聯合型別來表示型別的值的陣列K與那些型別的一起L。請注意,我們在實作 ( )中至少需要一個型別斷言const that = this as Equation<K | L>來說服編譯器開始添加L不在Equation<K>.
為了完整起見,這里是Activity,它還需要跟蹤其方程中的變數:
class Activity<K extends string = never> {
id: string;
equation: Equation<K>;
fixedValues: Partial<Record<K, number>>;
constructor() {
this.id = "test";
this.fixedValues = {};
this.equation = new Equation({ variables: [] });
}
public setEquation<L extends string>(equation: Equation<L>): Activity<L> {
const that = this as Activity<any> as Activity<L>;
that.equation = equation;
return that;
}
public addFixedValues(fixedValues: Partial<Record<K, number>>): this {
this.fixedValues = {
...this.fixedValues,
...fixedValues
};
return this;
}
}
現在我們可以演示流暢的界面,只有在操作不產生不同型別的值時才重用變數:
const equation1 = new Equation({ variables: ["a", "b"] })
const activity1 = new Activity().setEquation(equation1);
activity1.addFixedValues({ c: 20 }); // error!
// ----------------------> ~~~~~
// Object literal may only specify known properties, and 'c'
// does not exist in type 'Partial<Record<"a" | "b", number>>'
activity1.addFixedValues({ a: 20 });
const equation2 = equation1.addVariable("c"); // use new variable to represent new type
const activity2 = activity1.setEquation(equation2); // use new variable to represent new type
activity2.addFixedValues({ c: 20 }); // okay now
看起來不錯!
Playground 鏈接到代碼
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/369885.html
標籤:打字稿
上一篇:打字稿私有變數訊息
下一篇:在reactjs中生成隨機陣列
