打字稿:重载并检查泛型

时间:2020-07-31 21:40:07

标签: typescript

我有这个界面:

export interface ICRUDService<T extends IModel> {

    save(item: T): Promise<void>;
    save(items: T[]): Promise<void>;
    save(item: IRemoteData<T>): Promise<void>;
    save(items: IRemoteData<T>[]): Promise<void>;
    save(item: Partial<T>): Promise<void>;
}

和实现:

export abstract class AbstractCRUDServiceImpl<T extends IModel> implements ICRUDService<T> {

    async save(item: T): Promise<void>;
    async save(items: T[]): Promise<void>;
    async save(item: IRemoteData<T>): Promise<void>;
    async save(items: IRemoteData<T>[]): Promise<void>;
    async save(item: Partial<T>): Promise<void>;

    async save(item: any): Promise<void> {

        if (typeof item === T)
            // ...

    }
}

但是它说:

'T'仅指类型,但被用作值 here.ts(2693)'

解决这个问题的正确方法是什么?

1 个答案:

答案 0 :(得分:1)

请记住,当代码实际运行时,所有键入信息都将消失。因此,您不能依靠类型来确定运行时对象是什么。

相反,您必须确定某个值是否具有与所需类型相同的 features

第二,您的实现函数的参数应该是一个类型,该类型是重写中每种类型的并集。


假设您的IModelIRemoteData的设置如下:

interface IRemoteData<T> {
  remoteData: T
}
interface IModel {
  id: number
}

现在您将有一个这样的实现:

export abstract class AbstractCRUDServiceImpl<T extends IModel> implements ICRUDService<T> {
  async save(item: T): Promise<void>;
  async save(items: T[]): Promise<void>;
  async save(item: IRemoteData<T>): Promise<void>;
  async save(items: IRemoteData<T>[]): Promise<void>;
  async save(item: Partial<T>): Promise<void>;
  async save(items: IRemoteData<T> | T): Promise<void>; // Added this override

  async save(item: T | T[] | IRemoteData<T> | IRemoteData<T>[] | Partial<T>): Promise<void> {
    if (Array.isArray(item)) {
      // item is array in this scope, iterate over each item and save them
      for (const singleItem of item) {
        await this.save(singleItem)
      }
    } else {
      // item is not an array in this scope
      if ('id' in item) {
        item // T | Partial<T>
      } else {
        item // IRemoteData<T>
      }
    }
  }
}

在该条件的每个分支中,您将处理该类型。

请注意,您从未将其与类型进行比较,但是会看到它是否具有所需类型的功能。您可以使用Array.isArray()来查看它是否是一个数组,并且在有条件的打字稿中使用时知道这意味着它是一个数组,并且该类型在联合中不再可以是任何非数组类型。

您可以使用'propName' in item来测试它是否定义了仅可能存在于所需类型之一上的属性。

然后您可以使用else子句来匹配尚未过滤掉的任何类型。

Playground


现在请注意其他优先事项:

async save(items: IRemoteData<T> | T): Promise<void>; // Added this override

这是条件数组处理分支所必需的。问题是,在您知道它是数组之后,您不知道它是什么数组。因此,当迭代这些项目时,每个项目的类型为:

T | IRemoteData<T>

因此,您需要重载以处理该特定情况。

async save(items: IRemoteData<T> | T): Promise<void>; // Added this override

或者您可以完全消除覆盖。当您只有一个可能是类型的并集的参数时,覆盖并没有什么用,而某些参数签名返回不同的类型则更为有用。这是单个函数定义本身无法完成的事情。

例如:

function foo(a: number): string
function foo(a: string): number
function foo(a: number|string): number|string {
  if (typeof a === 'string') {
    return 123
  } else {
    return 'a string'
  }
}

此重载将某些参数类型与某些返回类型联系在一起。但是您的函数不需要它,可以将其表示为一个函数,其中参数只是许多事物的结合。

这一切都应该起作用:

export abstract class AbstractCRUDServiceImpl<T extends IModel> {
  async save(item: T | T[] | IRemoteData<T> | IRemoteData<T>[] | Partial<T>): Promise<void> {
    if (Array.isArray(item)) {
      // item is array in this scope, iterate over each item and save them
      for (const singleItem of item) {
        await this.save(singleItem)
      }
    } else {
      // item is not an array in this scope
      if ('id' in item) {
        item // T | Partial<T>
      } else {
        item // IRemoteData<T>
      }
    }
  }
}

Playground