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

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

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

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

其次,实现函数的参数应该是一个类型,它是重写中每个类型的并集。


假设您的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子句来匹配任何尚未筛选出的类型。

游乐场


现在注意额外的覆盖:

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

游乐场

最新更新