簡體   English   中英

從 object 和密鑰列表推斷 function 簽名

[英]Infer function signature from an object and a list of keys

我有一組指標表示為 object:

type Indicators = {
  UNIT_PRICE: number;
  QUANTITY: number;
  CURRENCY: "$" | "€";
  TOTAL: string;
};

我想描述對這組指標進行的計算:

type Computation<R extends { [key: string]: any }> = {
  output: keyof R;
  inputs: Array<keyof R>;
  // I Would like the arguments and the returned value to be type checked
  compute: (...inputs: any[]) => any;
};

我沒有設法表達inputs (指標名稱)和compute function 的 arguments 之間的關系。 對於outputcompute的返回類型也是如此。 我想在某些時候我必須使用映射類型,但我不知道如何。

我想要的是能夠編寫這種代碼,typescript 抱怨如果compute的簽名與從inputsoutput推斷的類型不匹配。

const computation: Computation<Indicators> = {
  output: "TOTAL",
  inputs: ["UNIT_PRICE", "QUANTITY", "CURRENCY"],
  compute: (unitPrice, quantity, currency) =>
    (unitPrice * quantity).toFixed(2) + currency.toUpperCase(),
};

操場

為了使它工作,您需要Computation是通用的,不僅在 object 類型中,而且在輸入鍵和 output 鍵的元組中。 (好吧,從技術上講,您可以嘗試將“輸入鍵和 output 鍵的每個可能的元組”表示為一個大聯合,但這很煩人並且不能很好地擴展,所以我忽略了這種可能性。)例如:

type Computation<T extends Record<keyof T, any>,
  IK extends (keyof T)[], OK extends keyof T> = {
    output: OK;
    inputs: readonly [...IK];
    compute: (...inputs: {
      [I in keyof IK]: T[Extract<IK[I], keyof T>]
    }) => T[OK];
  };

object類型是T (我是從R改過來的),輸入鍵是IK ,output鍵是OK的。 compute()方法的輸入參數列表將鍵元組映射到值類型元組。


但是,現在,為了注釋任何值的類型,這將是冗長和多余的:

const validComputation: Computation<
  Indicators,
  ["UNIT_PRICE", "QUANTITY", "CURRENCY"],
  "TOTAL"
> = {
  output: "TOTAL",
  inputs: ["UNIT_PRICE", "QUANTITY", "CURRENCY"],
  compute: (unitPrice, quantity, currency) =>
    (unitPrice * quantity).toFixed(2) + currency.toUpperCase(),
};

理想情況下,您希望編譯器為您推斷IKOK ,同時讓您指定T 但目前 TypeScript 不允許您部分指定類型; 您要么必須如上所述指定整個事物,要么讓編譯器為您推斷整個類型。 您可以創建一個助手 function 來讓編譯器為您推斷IKOK ,但同樣,任何特定的調用都只會推斷出所有TIKOK ,或者它們都不推斷。 這整個“部分推理”的事情在 TypeScript 中是一個未解決的問題; 請參閱microsoft/TypeScript#26242進行討論。

我們可以用當前語言做的最好的事情是編寫類似curried通用 function 之類的東西,其中您在初始 function 上指定T ,然后讓編譯器推斷IK並在調用返回的 ZC1C42545768E683841C1 時OK

const asComputation = <T,>() =>
  <IK extends (keyof T)[], OK extends keyof T>(c: Computation<T, IK, OK>) => c;

它的工作原理是這樣的:

const asIndicatorsComputation = asComputation<Indicators>();
    
const validComputation = asIndicatorsComputation({
  output: "TOTAL",
  inputs: ["UNIT_PRICE", "QUANTITY", "CURRENCY"],
  compute: (unitPrice, quantity, currency) =>
    (unitPrice * quantity).toFixed(2) + currency.toUpperCase(),
});

const wrongComputation = asIndicatorsComputation({
  output: "TOTAL",
  inputs: ["UNIT_PRICE"],
  compute: (unitPrice) => unitPrice.toUpperCase() // error!
});

您調用asComputation<Indicators>()來獲取一個新的助手 function ,它接受編譯器推斷的某些IKOKComputation<Indicators, IK, OK>值。 你可以看到你得到了期望的行為,其中compute()方法參數是上下文類型的,如果你做錯了什么,你會得到一個錯誤。

Playground 代碼鏈接

這個答案是由一個叫@webstrand 的人在 gitter 上提供的。

首先,映射類型可以表達outputinputscompute簽名之間的關系:

type Computation<
  R extends { [key: string]: any },
  // readonly is important to make typescript infer tuples instead of arrays later on
  I extends readonly (keyof R)[],
  O extends keyof R,
> = {
  output: O;
  inputs: I;
  compute: (...inputs: { [P in keyof I]: I[P] extends keyof R ? R[I[P]] : never }) => R[O];
};

此版本要求用戶明確說明類型。

然后是“雙重調用”黑客,因為 typescript 僅在 function 調用站點推斷類型:

function createComputation<R extends { [key: string]: any }>(): <I extends readonly (keyof R)[], O extends keyof R>(o: Computation<R, I, O>) => Computation<R, I, O> {
  return (x) => x;
}

現在,我們可以使用createComputation對所有內容進行類型檢查:

const validComputation = createComputation<Indicators>()({
  output: "TOTAL",
  inputs: ["UNIT_PRICE", "QUANTITY", "CURRENCY"] as const,
  compute: (unitPrice, quantity, currency) =>
    (unitPrice * quantity).toFixed(2) + currency.toUpperCase(),
});

暫無
暫無

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

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