如何声明泛型类为泛型?

3
我正在尝试设计一种声明函数的方法,该函数可以返回一个 Promise 或一个 rxjs Observable 或一个 most Stream,并具有特定的返回类型,但我不知道在 TypeScript 中如何声明这个函数。
通常情况下,我希望在我的代码中使用类似以下的语法:
class Action<T> {
    dispatcher: Function
    constructor(dist) {
        this.dispatcher = dist
    }

    f = <T>(s: string): T<string> => this.dispatcher('hello'+s)
}

这样,我会得到错误:'T不是泛型'
对我来说,T 可以是 Promise、Observable 或 Stream
如果我能够有类似这样的东西,我可以声明一个特定的调度程序,根据我的声明返回我想要的值。
const promiseDispacher = (x:any)=>Promise.resolve(x)
const observableDispacher = (x:any)=>Observable.of(x)

我想要的是当我调用函数f时,能够以不同的方式使用它。
 const a = new Action<Promise>(promiseDistpacher)
 a.f('random string').then((s: string)=>{
   ....
 })

或者
 const a = new Action<Observable>(observableDistpacher)
 a.f('random string').subscribe((s: string)=>{
   ....
 })

更新:

已更新

f 是一个执行某些操作并使用调度程序将结果包装在特定的 T(Promise 或 Stream)中,并将其返回到执行该函数的类的函数。


1
等等,f 应该是什么?你似乎既将其视为返回 T 的函数(如果我理解你对 Action 的实现),又将其视为 T 本身(当你执行 a.f.then() 等操作时)。无论如何,你可能想要像 class Action<T> 一样将 Action 设为泛型。但是,如果没有更多关于 f 的理解,具体细节就会让我困惑。 - jcalz
@jcalz 是的,你说得对。Action类是通用的,描述了调度程序将返回Promise或Stream,但我该如何声明函数f - Ricardo
如果 f 是一个函数,那么你为什么要尝试在它上面访问 then() 方法呢?它只能是一个函数或者(比如说)一个 Promise... 不能同时是两者。那么它是哪一个呢? - jcalz
它是一个根据类的声明和调度器返回 Promise 或 Observable 的函数。 - Ricardo
1
那么请编辑您的问题,不要调用a.f.then(...)。如果f是一个函数,您需要调用它,就像a.f(“something”)。then(...)一样。否则,我认为在函数返回一个东西和这个东西本身之间仍然存在一些混淆。 - jcalz
a.f(s) 中的s是什么?请确保您的代码构成了一个 [mcve]。 - jcalz
1个回答

2
我会尝试回答这个问题,尽管我不太确定您在做什么。首先,让我们将参数中传入的调度程序返回的通用类型指定为T,类似于Promise<any>Observable<any>ReadableStream<any>
class Action<T> {
  dispatcher: (x: any) => T;
  constructor(dist: (x: any) => T) {
    this.dispatcher = dist
  }
  f = (s: string) => this.dispatcher('hello' + s);
}

const promiseDispatcher = (x: any) => Promise.resolve(x)
const a = new Action(promiseDispatcher);
a.f("random string").then((s: string) => {
  // ...
}); // works

这现在可以工作了,对于 Action<T> 中的 f 的类型被推断为 (s: string) => T。由于 a 是一个 Action<Promise<any>>,所以 a.f(s) 是一个 Promise<any>。请注意,尽管 f 在其返回类型中有一个 T,但 f 本身并不是您认为的泛型函数。泛型参数在类上,一旦您拥有一个具体的类实例,它的 f 函数也是一个具体的函数类型。
这里的一个问题是,您可能不希望 a.f(s) 是一个 Promise<any>,而更希望它是一个 Promise<string>。 TypeScript 目前并不支持 高阶类型,因此没有一种 通用的 方法来将 T 定义为像 PromiseObservableReadableStream 这样的泛型类型。使用 条件类型,您可以选择一些硬编码的类型,如 Promise<any>Observable<any>ReadableStream<any>,并将它们分别转换为 Promise<string>Observable<string>ReadableStream<string>
type AppliedToString<T> =
  T extends Promise<any> ? Promise<string> :
  T extends Observable<any> ? Observable<string> :
  T extends ReadableStream<any> ? ReadableStream<string> :
  T;

现在,如果我们将 T 约束为那些硬编码的类型并使用上面的类型函数,我们应该得到类似于您想要的 f 的类型。
// constrain T
class Action<T extends Promise<any> | Observable<any> | ReadableStream<any>> {
  dispatcher: (x: any) => T;
  constructor(dist: (x: any) => T) {
    this.dispatcher = dist
  }
  // assert return type of f as AppliedToString<T>
  f = (s: string) => this.dispatcher('hello' + s) as AppliedToString<T>;
}
const promiseDispatcher = (x: any) => Promise.resolve(x);
const a = new Action(promiseDispatcher);
a.f("random string").then((s: string) => {
  // ...
}); // works

现在,如果您检查a.f的返回类型,您会发现它是一个Promise<string>
这种方法的缺点是需要维护硬编码列表,并且它不是特别类型安全,因为它允许您传递返回Promise<number>的调度程序并将其视为Promise<string>... 这将在运行时导致错误。
如果我想要更简单和类型安全,我会将`T`限制为`Promise`、`Observable`或`ReadableStream`,并且放弃映射,但要求传入的调度程序使用`string`作为输入,返回一个`T`。
class Action<T extends Promise<string> | Observable<string> | ReadableStream<string>> {
  dispatcher: (x: string) => T;
  constructor(dist: (x: string) => T) {
    this.dispatcher = dist
  }
  f = (s: string) => this.dispatcher('hello' + s);
}
// note that x is type string in this:
const promiseDispatcher = (x: string) => Promise.resolve(x)
const a = new Action(promiseDispatcher);
a.f("random string").then((s: string) => {
  // ...
})

好的,希望其中一个或多个能对你有所帮助。祝你好运!

我试图为容器类型想出一种类型推断方法,但我无法弄清如何使语法工作。例如,您可以推断特定容器的包含类型,如 type GetContained<T> = T extends Promise<infer U> ? U : never,但是您无法推断特定包含类型的容器,如 type GetContainer<T> = T extends (infer U)<string> ? U : never。有没有办法做到这一点? - Patrick Roberts
现在我想起来,容器并不是一种类型。它只是一个创建类型的不完整模板,所以你不能像那样提取它...我现在意识到我的问题并没有什么意义。 - Patrick Roberts
1
你的问题确实有意义,但是TypeScript并没有一个很好的答案。容器类型属于更高级别的种类。有一个GitHub问题讨论添加对高阶类型的支持,但谁知道它是否会发生。现在只能从硬编码的容器列表中选择,就像我上面所做的那样。 - jcalz
谢谢你的回答!目前我会采用你提供的解决方案。 - Ricardo

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接