[英]TypeScript how to enforce a type of multiple type options, Property 'X' does not exist on type 'Y'
[英]How to enforce a type for multiple arguments in Typescript?
我寫了一個 function 在兩個值之間進行插值。 它獲取初始值、結束值和 state。 例如,從 0 到 1,state: 0.7 將是 0.7,即 0.3 * 0 + 0.7 * 1 = 0.7。
我還希望它不僅支持數字,還支持對象。 例如,要從{x: 100, y: 100}
插值到{x: 500, y: 500}
,甚至使用深層對象: {scale: 0, position: {x: 100, y: 100}}
到{scale: 1, position: {x: 500, y: 500}
。
function 本身很簡單,但我正在為這些類型而苦苦掙扎。 我想要由 Typescript 強制執行的 3 件事:
{a: 0}
那么另一個必須是{a: <number>}
第 1 點)似乎正在使用最近的 Typescript 版本:
type NumericObject = number | {[k in string]: number | NumericObject};
但我不知道如何輸入插值 function。 最接近我的期望的是:
const interpolate = <T extends NumericObject> (from: T, to : T) => (state: number):T => {
它將返回參數鍵入兩個 arguments 的並集,這不是我所需要的。 這允許兩個具有不同結構的對象,如下所示:
interpolate({a: 1}, {b: 1})(1)
它應該拋出一個編譯時錯誤,說明兩個對象具有不同的鍵。 顯然,它應該支持任何 object 結構,但兩者 arguments 必須相同。
當我使用默認的 arguments 時,編譯器會拋出錯誤:
Type 'number' is not assignable to type 'T'. 'number' is assignable to the constraint of type 'T', but 'T' could be instantiated with a different subtype of constraint 'NumericObject'.
這是 TS 操場上的代碼: link
我覺得這在 Typescript 中應該是可能的,但我不知道如何做到這一點。 有沒有辦法正確輸入這個 function?
這實際上似乎是可能的,但你不能制作嵌套對象。 在這里試試。 您必須將類型和 function 簽名更改為:
type NumericObject<Keys extends string[]> = {[k in Keys[number]]: number};
function interpolate(from: number, to: number): (state: number) => number;
function interpolate<Keys extends string[]>(from: NumericObject<Keys>, to: NumericObject<Keys>): (state: number) => number;
function interpolate(from: any, to : any) {
// other code
}
由於簽名重載,您需要在此處使用 function。
編輯:我原來的答案不起作用,所以我發表了評論並對其進行了一些修改。 在這里試試。 它基本上重新映射所有屬性以創建新類型:
type NumericObject = number | { [k in string]: number | NumericObject };
type ToNumObj<T> = T extends object ? { [K in keyof T]: ToNumObj<T[K]> } : number;
const interpolate = <T extends NumericObject>(from: T, to: ToNumObj<T>) => (state: number): ToNumObj<T> => {
// code
}
鑒於您的要求
from
和to
是NumericObject
,其中to
具有與from
相同的(可能是嵌套的)鍵,返回類型也是如此; 和from
和to
,他們將分別被視為0
和1
, 我的傾向是給interpolate
一個帶有兩個調用簽名的重載function 類型:
interface Interpolate {
(): (state: number) => number;
<T extends NumericObject>(from: T, to: ToNumObj<T>): (state: number) => ToNumObj<T>;
}
type ToNumObj<T> = T extends object ? { [K in keyof T]: ToNumObj<T[K]> } : number;
第一個調用簽名將處理沒有 arguments 調用interpolate()
的情況。 它最終應該返回一個number
。
第二個調用簽名處理一般情況。 在這里,我們仍然像你一樣from
泛型類型T extends NumericObject
中給出,但是對於to
類型和返回類型,我們專門將from
( T
) 的類型轉換為具有相同結構但其非對象屬性擴大到number
( ToNumObj<T>
)。 這種轉換有幾個目的:
它賦予to
類型的T
比from
類型的T
更低的推理優先級。 因此,編譯器不會將to
和from
視為平等,並可能合成您不想要的聯合,而是傾向於from
單獨推斷T
,並僅檢查T
與to
。 因此,如果to
在from
中找不到屬性,您應該會看到錯誤而不是聯合。
返回ToNumObj<T>
可以防止T
的數字屬性或子屬性被推斷為數字文字類型的奇怪現象。 如果您調用interpolate(0, 1)
,並且T
被推斷為0
或0 | 1
0 | 1
,你當然不希望編譯器說它最終會返回0
或0 | 1
0 | 1
,因為它可能不會。 如果T
中有數字文字很好,但我們需要為返回類型擴展它們。
除了我添加了0
和1
默認值並在其中扔了一堆any
之外,該實現與您的實現幾乎相同。 編譯器無法真正驗證實現是否符合Interpolate
類型,因此嘗試毫無意義。 通過說from
, to
和返回類型在實現中都是any
,我告訴編譯器讓我按照我認為合適的方式實現 function 。 當然,如果我的實現實際上沒有按照Interpolate
所說的那樣做,這就是我的問題(或者你的問題,如果你使用它的話),所以請謹慎行事:
const interpolate: Interpolate = (from: any = 0, to: any = 1) => (state: number): any => {
const interpolateValue = (v1: NumericObject, v2: NumericObject, s: number): NumericObject => {
if (typeof v1 === "number") {
return v1 * (1 - s) + (v2 as number) * s;
} else {
return Object.fromEntries(Object.keys(v1).map((k) => [k, interpolateValue(v1[k], (v2 as any)[k], s)]));
}
}
return interpolateValue(from, to, state);
}
讓我們看看這些例子:
console.log(
interpolate(0, 1)(0.3).toFixed(2) // "0.30"
);
console.log(
interpolate({ a: 0 }, { a: 1 })(0.4).a // 0.4
);
console.log(
interpolate(
{ scale: 0, position: { x: 100, y: 100 } },
{ scale: 1, position: { x: 500, y: 500 } })
(0.5).position.y // 300
);
console.log(
interpolate()(0.3).toFixed(2) // "0.30"
)
interpolate({ a: 1 }, { b: 1 })(1); // error!
// -------------------> ~~~~
// Argument of type '{ b: number; }' is not assignable
// to parameter of type '{ a: number; }'.
這一切對我來說都很好。 有效調用被視為有效,調用的返回類型被正確鍵入。 無效調用被視為無效,並有一個錯誤告訴您究竟出了什么問題。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.