首页 > 解决方案 > 无法键入从抽象类泛型方法扩展的类方法 (TypeScript)

问题描述

所以我有一个实现接口的抽象类:

export default interface ResponsibilityChainHandler {
  setNext(handler: ResponsibilityChainHandler): ResponsibilityChainHandler

  handle<T>(request: T): Promise<T>
}
import ResponsibilityChainHandler from './responsibility-chain.handler.interface'

export default abstract class AbstractResponsibilityChainHandler
  implements ResponsibilityChainHandler {
  private nextHandler?: ResponsibilityChainHandler

  public setNext(handler: ResponsibilityChainHandler): ResponsibilityChainHandler {
    this.nextHandler = handler
    return this.nextHandler
  }

  public async handle<T>(request: T): Promise<T> {
    if (this.nextHandler) {
      return this.nextHandler.handle(request)
    }
    return request
  }
}

现在,我想定义一个只接受特定类型对象GreetingHandlerRequest(定义为类)的新类。

import AbstractResponsibilityChainHandler from '../../domain/responsibility-chain.handler.abstract'
import GreetingHandlerRequest from '../../domain/greeting-handler-request.domain';

export default class WelcomeHandler extends AbstractResponsibilityChainHandler {
  public async handle<GreetingHandlerRequest>(
    request: GreetingHandlerRequest
  ): Promise<GreetingHandlerRequest> {
    return super.handle(request)
  }
}

但是,我在GreetingHandlerRequest导入时收到以下错误:

'GreetingHandlerRequest' is declared but its value is never read.

如果我尝试在方法内部做一些事情,就像request.existingAttr它抱怨的那样:

Property 'existingAttr' does not exist on type 'GreetingHandlerRequest'.

但是该类GreetingHandlerRequest有一个可公开访问的属性,称为existingAttr.

我也尝试这样做import type,但没有成功。我已经被困了一段时间了。问一些伙伴和谷歌搜索对我来说没有结果。有什么想法吗?

标签: typescriptgenericsinterfaceabstract-class

解决方案


TypeScript中有两种不同风格的泛型:泛型函数和泛型类型


泛型函数在函数的调用签名上声明其泛型类型参数(或多个参数),就像handle这里的方法:

interface RCHGenFunc {
  handle<T>(request: T): Promise<T>
}

在实际调用函数之前,不会指定泛型函数(T上面)的类型参数,此时调用者指定它(或编译器代表调用者推断它)。这意味着通用函数实现必须能够处理T函数调用者想要的任何可能的规范。


泛型类型在类型声明中声明其泛型类型参数(或多个参数),就像RCHGenType这里的类型:

interface RCHGenType<T> {
  handle(request: T): Promise<T>
}

必须先指定泛型类型(T上面)的类型参数,然后才能获得该类型的值。如果泛型类型具有引用类型参数的方法(如上handle()所示),则在调用该方法之前,一旦指定了周围的泛型类型,该类型参数就会被固定。一旦你在谈论,比如说 an RCHGenType<GreetingHandlerRequest>,那么它的handle()方法只需要能够处理 a GreetingHandlerRequest。因此,要实现该功能,您不需要处理T.


当您拥有具有泛型方法的特定类型而不是具有特定方法的泛型类型时,区别可能会有些模糊。重要的部分是泛型的范围。如果我把类型系统中的泛型拿来和 JavaScript 中的函数做一个类比的话,区别是这样的(下面是 JS,不一定是 TS):

const rchGenFunc = () => (T) => "Promise<" + T + ">";
const rchGenType = (T) => () => "Promise<" + T + ">";

它们每个都有一个名为 的参数T,但它们具有不同的作用域。在rchGenFunc我没有指定T何时调用它;相反,我不带参数调用它,然后返回一个可以接受任何参数的函数。但是在rchGenType我必须指定T何时调用它,然后取回仅适用于该特定T.


支持您的问题:您正在使用泛型函数,但您实际上应该使用泛型类型。即使在您的 中WelcomeHandler,您的handle()方法也是通用的,其中GreetingHandlerRequest只是一个巧合命名的类型参数,并且与您可能正在导入的任何接口/类无关GreeingHandlerRequest(这就是您收到“未使用”警告的原因)。

因此,让我们将类型参数从调用签名中移到类型中:

interface ResponsibilityChainHandler<T> {
  setNext(handler: ResponsibilityChainHandler<T>): ResponsibilityChainHandler<T>
  handle(request: T): Promise<T>
}

这意味着AbstractResponsibilityChainHandler也应该是通用的:

abstract class AbstractResponsibilityChainHandler<T>
  implements ResponsibilityChainHandler<T> {
  private nextHandler?: ResponsibilityChainHandler<T>

  public setNext(handler: ResponsibilityChainHandler<T>): ResponsibilityChainHandler<T> {
    this.nextHandler = handler
    return this.nextHandler
  }

  public async handle(request: T): Promise<T> {
    if (this.nextHandler) {
      return this.nextHandler.handle(request)
    }
    return request
  }
}

最后,我们可以谈谈WelcomeHandler。假设GreetingHandlerRequest看起来像这样:

interface GreetingHandlerRequest {
  existingAttr: string;
}

然后WeclomeHandler看起来像这样:

class WelcomeHandler extends AbstractResponsibilityChainHandler<GreetingHandlerRequest> {
  public async handle(request: GreetingHandlerRequest): Promise<GreetingHandlerRequest> {
    request.existingAttr.toUpperCase();
    return super.handle(request)
  }
}

在这里,WelcomeHandler它本身并不是一个泛型类。它是一个特定的类,你通过扩展得到AbstractResponsibilityChainHandler<GreetingHandlerRequest>,它是你通过插入特定GreetingHandlerRequest类型作为类型参数得到T的特定类型AbstractResponsbilityChainHandler<T>

现在WelcomeHandler'handle()方法只接受GreetingHandlerRequest参数。您不必将其设为通用(其声明中没有类型参数),并且GreetingHandlerRequest由于此限制,您可以在实现内部访问特定属性。


如果您说您确实想handle()成为一个通用方法并处理任何可能的请求类型,那么我们将不得不放弃类层次结构(因为 aWelcomeHandler不能正确处理handle()所有请求),或者我们必须让它接受所有请求,但在其中放置某种运行时检查,以便它不执行任何操作,除非它获得正确类型的请求。但这不是您想要的(谢天谢地),因此上述解决方案可能适合您。

Playground 代码链接


推荐阅读