繁体   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