简体   繁体   English

Typescript:重载和检查泛型

[英]Typescript: overloading and checking against generic type

I have this interface:我有这个界面:

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>;
}

and an implementation:和一个实现:

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)
            // ...

    }
}

But it says:但它说:

'T' only refers to a type, but is being used as a value here.ts(2693)' 'T' 仅指一种类型,但在此处用作值。ts(2693)'

What would be the proper way to solve this?解决这个问题的正确方法是什么?

Remember that all typing information will be gone when the code is actually running.请记住,当代码实际运行时,所有输入信息都将消失。 So you can't rely on types to figure what an object is at runtime.因此,您不能依靠类型来确定 object 在运行时是什么。

Instead, you have to determine if a value has the same features as the type you want.相反,您必须确定一个值是否与您想要的类型具有相同的特征

Secondly, the argument of your implementation function should be a type that is a union of every type from the overrides.其次,您的实现 function 的参数应该是覆盖中每种类型的联合类型。


Let's say your IModel and IRemoteData are setup like this:假设您的IModelIRemoteData设置如下:

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

Now you would have an implementation like this:现在你会有一个这样的实现:

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>
      }
    }
  }
}

In each branch of that conditional, you would do your handling of that type.在该条件的每个分支中,您将处理该类型。

Note that you never compare it against the types, but you see if it has features of the type you want.请注意,您永远不会将其与类型进行比较,但您会查看它是否具有您想要的类型的功能。 You can use Array.isArray() to see if it's an array, and when used in a conditional typescript knows that means it's an array, and that type can no longer be any non array type in the union.您可以使用Array.isArray()来查看它是否是一个数组,并且在条件 typescript 中使用时知道这意味着它是一个数组,并且该类型不能再是联合中的任何非数组类型。

You can use 'propName' in item to test if a it defines a property that may only exist on one of the types you want.您可以'propName' in item来测试它是否定义了一个可能只存在于您想要的类型之一的属性。

And then you can use the else clause to match any type that you haven't yet filtered out.然后您可以使用else子句来匹配您尚未过滤掉的任何类型。

Playground 操场


Now note the additional override:现在注意额外的覆盖:

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

This is needed for the array handling branch of the conditional.这是条件的数组处理分支所需要的。 The problem is that after you know it's array, you don't know what it's an array of.问题是,在你知道它是数组之后,你不知道它是什么数组。 So when iterating over the items, the type of each item is:所以在迭代项目时,每个项目的类型是:

T | IRemoteData<T>

So you need an overload to handle that specific case.所以你需要一个重载来处理那个特定的情况。

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

Or you could eliminate the overrides entirely.或者您可以完全消除覆盖。 Overrides aren't as useful when you just have one argument that could be a union of types, and much more useful certain argument signatures return different types.当您只有一个可能是类型联合的参数时,覆盖就没有那么有用了,并且更有用的某些参数签名返回不同的类型。 This is something a single function definition on it's own couldn't do.这是单个 function 定义本身无法做到的事情。

For instance:例如:

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'
  }
}

This overload ties certain argument types to certain return types.此重载将某些参数类型与某些返回类型联系起来。 But your function doesn't need that, and can be expressed as a single function where it's argument is simply a union of many things.但是您的 function 不需要,并且可以表示为单个 function ,其中它的论点只是许多事物的结合。

All that means this should work:所有这意味着这应该工作:

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 操场

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM