簡體   English   中英

來自通用參數的打字稿對象鍵

[英]Typescript object keys from generic parameters

我正在嘗試創建一個函數,該函數接受一組對象,然后是一個或多個參數,這些參數指定將在該數組上使用的屬性。

例如,以下任一:

update([{ name: 'X', percentage: 1, value: 2 }], 'percentage', 'value');
update([{ name: 'X', foo: 1, bar: 2 }], 'foo', 'bar');

嘗試1:

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)

嘗試2:

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>定義這樣的東西來表示已知在AB鍵處具有number屬性的東西。 僅僅說ABkeyof 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] 所以我們只希望dataEntry<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');

Playground 代碼鏈接

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM