[英]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 之間的關系。 對於output
和compute
的返回類型也是如此。 我想在某些時候我必須使用映射類型,但我不知道如何。
我想要的是能夠編寫這種代碼,typescript 抱怨如果compute
的簽名與從inputs
和output
推斷的類型不匹配。
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(),
};
理想情況下,您希望編譯器為您推斷IK
和OK
,同時讓您指定T
。 但目前 TypeScript 不允許您部分指定類型; 您要么必須如上所述指定整個事物,要么讓編譯器為您推斷整個類型。 您可以創建一個助手 function 來讓編譯器為您推斷IK
和OK
,但同樣,任何特定的調用都只會推斷出所有T
、 IK
和OK
,或者它們都不推斷。 這整個“部分推理”的事情在 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 ,它接受編譯器推斷的某些IK
和OK
的Computation<Indicators, IK, OK>
值。 你可以看到你得到了期望的行為,其中compute()
方法參數是上下文類型的,如果你做錯了什么,你會得到一個錯誤。
這個答案是由一個叫@webstrand 的人在 gitter 上提供的。
首先,映射類型可以表達output
、 inputs
和compute
簽名之間的關系:
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.