簡體   English   中英

如何在 Typescript 中強制執行多個 arguments 的類型?

[英]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 件事:

  1. from 和 to arguments 必須是數字或帶數字的對象(深)
  2. 兩個 arguments 必須具有相同的結構。 例如,如果第一個是{a: 0}那么另一個必須是{a: <number>}
  3. 如果沒有提供兩個 arguments(或后一個),則它們分別默認為 0 和 1。

第 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
}

鑒於您的要求

  • fromtoNumericObject ,其中to具有與from相同的(可能是嵌套的)鍵,返回類型也是如此;
  • 應該允許一個人fromto ,他們將分別被視為01

我的傾向是給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 ) 的類型轉換為具有相同結構但其非對象屬性擴大到numberToNumObj<T> )。 這種轉換有幾個目的:

  • 它賦予to類型的Tfrom類型的T更低的推理優先級 因此,編譯器不會將tofrom視為平等,並可能合成您不想要的聯合,而是傾向於from單獨推斷T ,並僅檢查Tto 因此,如果tofrom中找不到屬性,您應該會看到錯誤而不是聯合。

  • 返回ToNumObj<T>可以防止T的數字屬性或子屬性被推斷為數字文字類型的奇怪現象。 如果您調用interpolate(0, 1) ,並且T被推斷為00 | 1 0 | 1 ,你當然不希望編譯器說它最終會返回00 | 1 0 | 1 ,因為它可能不會。 如果T中有數字文字很好,但我們需要為返回類型擴展它們。


除了我添加了01默認值並在其中扔了一堆any之外,該實現與您的實現幾乎相同。 編譯器無法真正驗證實現是否符合Interpolate類型,因此嘗試毫無意義。 通過說fromto和返回類型在實現中都是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; }'.

這一切對我來說都很好。 有效調用被視為有效,調用的返回類型被正確鍵入。 無效調用被視為無效,並有一個錯誤告訴您究竟出了什么問題。

Playground 代碼鏈接

暫無
暫無

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

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