[英]Typescript object keys from generic parameters
我正在嘗試創建一個函數,該函數接受一組對象,然后是一個或多個參數,這些參數指定將在該數組上使用的屬性。
例如,以下任一:
update([{ name: 'X', percentage: 1, value: 2 }], 'percentage', 'value');
update([{ name: 'X', foo: 1, bar: 2 }], 'foo', 'bar');
function update<T extends { name: string }, A extends keyof T, B extends keyof T>(data: T[], a: A, b: B) {
for (const row of data) {
console.log(row.name, row[a] * row[b]);
row[a] = row[a] * row[b];
}
}
row[a]
作為數字時,我得到一個錯誤,因為類型是T[A]
並且似乎它不知道這是一個數字row[a]
分配一個數字時,我得到Type 'number' is not assignable to type 'T[A]'.(2322)
type Entry<A extends string, B extends string> = { name: string } & Record<A | B, number>;
function update<T extends Entry<A, B>, A extends string, B extends string>(data: T[], a: A, b: B) {
for (const row of data) {
console.log(row.name, row[a] * row[b]);
row[a] = row[a] * row[b];
}
}
row[a]
時的問題,我將它用作數字似乎沒問題Type 'number' is not assignable to type 'T[A]'.(2322)
當試圖將一個數字分配給row[a]
(或任何事情)
Entry
中刪除{ name: string }
也不能解決這個問題,打字稿仍然不允許我出於某種原因在這里分配任何東西這在 JS 中似乎是一件相當普遍的事情,但我不知道如何讓 Typescript 理解它。
您可以創建映射類型 ( https://www.typescriptlang.org/docs/handbook/2/mapped-types.html ) 來處理數字值,然后添加{name: string}
。 不幸的是,您必須將乘法結果轉換為T[keyof T]
,因為 TS 無法確定只能將數字分配給非name
字段。
update([{ name: 'X', percentage: 1, value: 2 }], 'percentage', 'value');
update([{ name: 'X', foo: 1, bar: 2 }], 'foo', 'bar');
type NumberValues<Type> = {
[Property in keyof Omit<Type, 'name'>]: number;
};
function update<T extends NumberValues<T> & {name: string}>(data: T[], a: keyof T, b: keyof T) {
for (const row of data) {
const aValue = row[a];
console.log(row.name, row[a] * row[b]);
row[a] = row[a] * row[b] as T[keyof T];
}
}
從那里您可以修改update
方法以接受keyof T
類型的多個參數,如下所示:
function update<T extends NumberValues<T> & {name: string}>(data: T[], ...args: (keyof T)[]) {
for (const row of data) {
console.log(row.name, row[args[0]] * row[args[1]]);
row[args[0]] = row[args[0]] * row[args[1]] as T[keyof T];
}
}
如果要避免強制轉換,可以將提取的行保存到可以鍵入的變量中,類似於@jcalz 解決方案:
function update<T extends NumberValues<T> & {name: string}>(data: T[], ...args: (keyof T)[]) {
for (const row of data) {
const r: {[property in keyof T]: number} = row;
r[a] = row[a] * row[b];
}
}
我不確定您是否以此為目標,但建議的解決方案允許您向對象添加多個字段,並且並非傳遞所有鍵,例如:
update([{ name: 'X', percentage: 1, value: 2, anotherVal: 5 }], 'percentage', 'value');
也會起作用。
您肯定需要像Entry<A, B>
定義這樣的東西來表示已知在A
和B
鍵處具有number
屬性的東西。 僅僅說A
和B
是keyof typeof data
是不夠的,因為所有編譯器都知道, keyof typeof data
可能存在非數字屬性(實際上, name
屬性是非數字屬性)。
此外,您不希望data
屬於通用類型T extends Entry<A, B>
。 該約束是一個上限,因此T
可能是非常具體的東西,例如{name: string, pi: 3.14, e: 2.72}
具有number
literal type的屬性。 可分配給類型3.14
的唯一值是3.14
,因此編譯器正確地阻止您將row[a] * row[b]
分配給row[a]
。 所以我們只希望data
是Entry<A, B>
類型,而不是Entry<A, B>
<A, B> 的某個未知子類型。 這給了我們這個:
function update<A extends string, B extends string>(data: Entry<A, B>[], a: A, b: B) {
for (const row of data) {
console.log(row.name, row[a] * row[b]);
row[a] = row[a] * row[b]; // error! Type 'number' is not assignable to type 'Entry<A, B>[A]'
}
}
這仍然打破了分配。 編譯器無法看到該number
可分配給Entry<A, B>[A]
。 十字路口的某些東西使它感到困惑。 我認為這是 TypeScript 的限制(盡管有人可能會爭辯說,如果A
是"name"
,那么Entry<A, B>[A]
的類型是number & string
,也稱為never
,因此編譯器是正確的警告您number
不一定可以分配給Entry<A, B>[A]
,但在我看來,這非常迂腐,在大多數情況下並不是特別有用)。 我還沒有在 GitHub 上找到一個特別的問題來談論它。
另一方面,編譯器可以看到number
可分配給Record<A, number>[A]
。 由於Entry<A, B>
是Record<A, number>
Record<A, number>
"name"
類型,因此我們可以通過將row
分配給帶有適當的類型注釋:
function update<A extends string, B extends string>(data: Entry<A, B>[], a: A, b: B) {
for (const row of data) {
console.log(row.name, row[a] * row[b]);
const r: Record<A, number> = row; // okay
r[a] = row[a] * row[b];
}
}
現在一切都按預期工作:
update([{ name: 'X', percentage: 1, value: 2 }], 'percentage', 'value');
update([{ name: 'X', foo: 1, bar: 2 }], 'foo', 'bar');
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.