在打字稿中不同实例的数组元素上调用方法?

Call method on the element of the array of the different instances in typescript?

我正在使用打字稿并遇到这样的问题。我有一个 classes 列表和它的实例数组。这些 classes 从一些基本的 class 扩展而来。每个 class 都有自己的方法。我想在数组的元素上调用这些自己的方法。但是我得到了一个错误。

这是一个example:

class BasicClass { 
    getName() { 
        console.log('My name is BasicClass');
    }
}

class Child1 extends BasicClass { 
    getChildOneSpecialName() { 
        console.log('My getChildOneSpecialName is Child1');
    }
}

class Child2 extends BasicClass { 
    getSpecialName() { 
        console.log('My special name is Child2');
    }
}

class Child3 extends BasicClass { 
    getStrangeName() { 
        console.log('My getStrangeName is Child1');
    }
}


const array = [new Child1(), new Child2(), new Child3()];
array[1].getSpecialName(); // Property 'getSpecialName' does not exist on type 'Child1 | Child2 | Child3'.
array[2].getStrangeName(); // Property 'getStrangeName' does not exist on type 'Child1 | Child2 | Child3'.

我应该怎么做才能让它成为可能?

因为数组被推断为 BasicClass[] 类型,这实际上是正确的。现在 basic class 既没有 strangeName 也没有 specialName。在调用之前,您需要检查类型:

 if(arr[1] instanceof Child2)
   arr[1].getSpecialName()

您有两个选择:

1。您可能想要重构 class 层次结构,因此您可以调用一个通用方法,该方法将获得适合 class:

的特殊或奇怪或(等)名称
class BasicClass { 
    getName() { 
        console.log('My name is BasicClass');
    }
    getCustomName() {
        return this.getName();
    }
}

class Child1 extends BasicClass { 
    getCustomName() { 
        console.log('My getChildOneSpecialName is Child1');
    }
    // Or keep `getChildOneSpecialName` and have `getCustomName` call it
}

class Child2 extends BasicClass { 
    getCustomName() { 
        console.log('My special name is Child2');
    }
    // Or keep `getSpecialName` and have `getCustomName` call it
}

class Child3 extends BasicClass { 
    getCustomName() { 
        console.log('My getStrangeName is Child1');
    }
    // Or keep `getStrangeName` and have `getCustomName` call it
}


const array = [new Child1(), new Child2(), new Child3()];
array[1].getCustomName();
array[2].getCustomName();

您还可以向 array 添加类型注释:

const array : Array<BasicClass> = [new Child1(), new Child2(), new Child3()];

...但是 TypeScript happily infers it,尽管它可能推断出 Child1 | Child2 | Child3 而不是 BasicClass

2。或者,检查类型并酌情转换,但这通常不是最佳做法:

const array = [new Child1(), new Child2(), new Child3()];
if (array[1] instanceof Child2) {
    (array[1] as Child2).getSpecialName();
}
if (array[2] instanceof Child3) {
    (<Child3>array[2]).getStrangeName();
}

请注意,TypeScript 提供了两种转换方式;我将以上两个都用作示例。

好吧,在您的示例中,唯一的方法是为每个 项进行转换。如果你想为它们中的任何一个调用公共方法,你可以将你的数组定义为:

const array: Array<BasicClass> = [new Child1(), new Child2(), new Child3()];
array[1].getName(); // correct

但是,TypeScript 现在不知道特定索引的值为 class Child1,因此它不会让您调用该方法。在你的情况下,你最好每次都投:

(<Child2>array[1]).getSpecialName()

或者您可以将数组定义为 any[]:

const array: any[] = [new Child1(), new Child2(), new Child3()];
array[1].getSpecialName(); // correct

这让你可以做任何你想做的事情,由你自己负责,但你也失去了编译器的帮助。

从 T.J 的谈话中,我要指出几个关于这种情况下类型推断的问题。

如T.J。指出,确实不需要将 array 声明为 Array<BasicClass>(或 BasicClass[],两者相同)。如果不这样做,TypeScript 会将数组的类型假定为 (Child1 | Child2 | Child3)[].

在这种情况下,如果您这样做 array[0].getName(),效果会很好。在这两种情况下,TypeScript 都知道数组的每个元素都有一个方法 getName。如果你不指定数组的类型,它知道因为 Child1Child2Child3 有这样的方法(不是因为它们扩展了 BasicClass)。

但是,假设我定义了两个新的 classes:

class Child4 extend BasicClass {
    getAnotherName() {
        console.log('anything');
    }
}

class Other {
    getName() { console.log('other'); }
    getStrangeName() { }
}

现在我们用两种方式定义数组:

const array1: BasicClass[] = [new Child1(), new Child2(), new Child3()];
const array2 = [new Child1(), new Child2(), new Child3()];
array1[0].getName(); // fine
array2[0].getName(); // fine

但是,让我们这样做:

array1.push(new Child4()); // fine. Child4 is a subclass of BasicClass
array1.push(new Child4()); // error! Child4 is not compatible with (Child1 | Child2 | Child3)!

如果我们这样做:

array1.push(new Other()); // Works! Why??
array2.push(new Other()); // also works.

如果您想知道为什么最后一个示例在这两种情况下都有效,是因为 OtherBasicClass 都兼容(它的所有方法和属性都相同签名)。而且它还兼容 (Child1 | Child2 | Child3) 因为它在结构上兼容 Child3.

但是,如果您从Other中删除getStrangeName方法,您仍然可以将其分配给BasicClass[],因为它在结构上仍然与BasicClass兼容。但是,将其分配给 array2 会失败,因为它与 Child1Child2Child3.

都不兼容

所以,归根结底,TypeScript 中的类型重要的是类型的 结构 ,而不是名称或它们是否相互派生。