簡體   English   中英

使用 Typescript Generics 限制函數輸入參數

[英]Using Typescript Generics to restrict a functions input parameter

問題

我創建了一個 Typescript package 在我的后端節點 firebase 雲函數和調用它們的前端 React 客戶端之間共享類型。 下面是一些類型的示例。

interface FirstFunctionInput {
  x: number
}

interface SecondFunctionInput {
  y: string
}

// defines all available functions
enum FirebaseFunction {
  FirstFunction = "firstFunction",
  SecondFunction = "secondFunction"
}

這就是我目前使用firebase sdk調用函數的方式:

firebase.funtions.httpsCallable('firstFunction')({ x: 21 })
  .then(result => { ... })

我的問題是

  1. 不能保證 function 'firstFunction' 存在
  2. 即使是這樣,我怎么能確定它接受諸如{ x: 21 }之類的參數。

所以我想要實現的是一種使用 Generics 來克服這些問題的實用方法。

理想的解決方案

下面是我(理想情況下)如何看待 API 的示例。 我提供了一個示例,其中向 function 提供了錯誤的輸入類型,並且我希望 Typescript 顯示錯誤。

callFirebaseFunction<FirstFunction>({ y: 'foo' })
                                    ^----------^
                    show error here, since FirstFunction must take a FirstFunctionInput

當前(但還沒有)解決方案

我已經接近實現這一目標,API 可以工作,但是它需要在調用位置指定輸入參數:

callFirebaseFunction<FirstFunctionInput>(
  FirebaseFunction.FirstFunction,
  { x: 21 }
)
.then(result => { ... })

這並不理想,因為程序員需要知道輸入類型才能指定它。

最后的話

我嘗試了所有不同的擴展接口組合、擴展的抽象類甚至構建器模式,但我找不到將這些組合在一起的方法。 這樣的事情甚至可能嗎?

由於 httpsCallable 中沒有使用泛型,我們應該用httpsCallable包裝它並使用泛型來限制類型。

這里會用到一些關於Currying的知識,這樣,它的結構就和原來的一樣了。

interface FirstFunctionInput {
  x: number
}

interface SecondFunctionInput {
  y: string
}

interface FunctionInputs {
  firstFunction: FirstFunctionInput,
  secondFunction: SecondFunctionInput
}

function callFirebaseFunction<K extends keyof FunctionInputs>(fn: K) {
  return function (input: FunctionInputs[K]) {
    return firebase.functions().httpsCallable(fn)(input);
  };
};

callFirebaseFunction('firstFunction')({ x: 1 }) // OK
callFirebaseFunction('secondFunction')({ y: '1' }) // OK
callFirebaseFunction('test')({ x: 1 }) // ERROR
callFirebaseFunction('secondFunction')({ x: true }) // ERROR

額外更新

如果要指定output的類型,也就是說

allFirebaseFunction('firstFunction')({ x: 1 }).then((res) => /* specify type of res */)

由於HttpsCallableResult的類型在源碼中是這樣一個接口:

export interface HttpsCallable {
    (data?: any): Promise<{data: any}>;
  }

那么擴展{data: any}的 output 的類型會更好。

最后,我們可以實現如下:

interface FunctionInputs {
  firstFunction: {
    input: FirstFunctionInput,
    output: { data: number }
  },
  secondFunction: {
    input: SecondFunctionInput,
    output: { data: boolean }
  }
}

function callFirebaseFunction<K extends keyof FunctionInputs>(fn: K) {
  return function (input: FunctionInputs[K]['input']): Promise<FunctionInputs[K]['output']> {
    return firebase.functions().httpsCallable(fn)(input);
  };
};

callFirebaseFunction('firstFunction')({ x: 1 }).then(res => { /* { data: number } */ })
callFirebaseFunction('secondFunction')({ y: '1' }).then(res => {/* { data: boolean } */ })

暫無
暫無

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

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