【问题标题】:Call method on the element of the array of the different instances in typescript?在打字稿中不同实例的数组元素上调用方法?
【发布时间】:2018-03-23 10:48:50
【问题描述】:

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

这是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'.

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

【问题讨论】:

  • 欢迎来到 Stack Overflow!您问题的全部内容必须在您的问题中in,而不仅仅是链接。链接腐烂,使问题及其答案对未来的人们毫无用处,人们不应该离开现场来帮助你。在问题中添加minimal reproducible example ,最好使用 Stack Snippets(<> 工具栏按钮)使其可运行(here's how to do one)。更多:How do I ask a good question?(但遗憾的是,sn-ps 没有对 TypeScript 的内置支持。)
  • 这次我已将代码复制到问题中。

标签: javascript typescript oop ecmascript-6 typing


【解决方案1】:

嗯,在您的示例中,唯一的方法是为 每个 项目进行转换。如果你想调用其中任何一个的通用方法,你可以将你的数组定义为:

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

但是,TypeScript 现在不知道特定索引具有类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&lt;BasicClass&gt;(或BasicClass[],相同)。如果不这样做,TypeScript 将假定数组的类型为 (Child1 | Child2 | Child3)[]。

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

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

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.

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

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

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

【讨论】:

  • 在array 上添加类型注释显然没有坏处,但 TypeScript 很高兴地推断出它。
  • 确实如此,但它假定类型为(Child1 | Child2 | Child3)[],这是真的,但对我的口味来说不是很整洁
  • 有趣,我在想BasicClass(最近的共同祖先)。但我对 TypeScript 的类型推断是如何工作的并不是那么深入。你碰巧有一个链接可以告诉我它是“或”吗?
  • 我想我在实践中知道,因为我经常使用 TypeScript。最后,TypeScript 中的类型推断非常简单:您声明一个包含三个对象的数组?好吧,那么它的类型是您添加的所有不同类型的数组。与对象相同。如果您定义 const ob = {name: string, age: number},则 ob 的类型将是 {name: string, age: number},因为 TypeScript 在处理类型时使用结构相等性。例如,它允许您将类型为 (Child1 | Child2 | Child3)[] 的对象分配给类型为 BasicClass[] 的变量,因为所有类型都有 getName。
  • 事实上,我将扩展我的答案,指出指示类型为 Array&lt;BasicClass&gt; 或不这样做之间的一些关键区别。
【解决方案2】:

因为数组被推断为类型BasicClass[],这实际上是正确的。现在基本类既没有奇怪的名字也没有特殊的名字。在调用之前,您需要检查类型:

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

【讨论】:

  • TypeScript 仍然会抱怨 arr[1].getSpecialName 调用。
  • 感谢您的快速回复。我仍然收到相同的 tslint 错误
  • 奇怪。不是类型保护的实例吗?
  • @JonasW.:是的,但是 TypeScript 的编译器不知道在 if 中,你知道 arr[1] 是 Child2,即使你在运行时知道。正如我在回答中所说,你需要一个演员表(如果你这样做,这通常不是最佳做法)。
  • @t.j。但实际上 instanceof 是一个类型保护typescriptlang.org/docs/handbook/advanced-types.html。也许打字稿不能在数组中处理它。
【解决方案3】:

你有几个选择:

1。您可能想要重构该类层次结构,以便您可以调用一个通用方法,该方法将获取适合该类的特殊或奇怪或(等)名称:

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 提供了两种转换方式;以上两个我都用过。

【讨论】:

  • 我不能使用 1-st 变体,因为子类具有不应该在 BasicClass 中的特定方法。我猜想没有优雅的解决方案,所以我将使用 2-nd 变体。谢谢
猜你喜欢
  • 2017-02-25
  • 2019-11-15
  • 2018-05-25
  • 2018-02-24
  • 1970-01-01
  • 2019-03-02
  • 2021-09-13
  • 2016-10-26
  • 1970-01-01
相关资源
最近更新 更多