【问题标题】:Why the need to redefine properties when implementing an interface in TypeScript?为什么在 TypeScript 中实现接口时需要重新定义属性?
【发布时间】:2020-09-19 02:01:36
【问题描述】:

我正在研究类和接口。我有一件事情让我很恼火,这是我第一次处理这类事情,所以请耐心等待..

假设我得到了这个界面:

// IFoo.d.ts
export default interface IFoo {
  foo: string;
  bar: number;
}

当我在一个类中实现它时,我会执行以下操作:

// FooModel.ts
import IFoo from './IFoo';

export default class FooModel implements IFoo {
  foo: string;
  bar: number;

  constructor({ foo, bar }: IFoo = { foo: 'hello', bar: 1 }) {
    this.foo = foo;
  }
}

为什么我必须重新实现相同的属性?

这与复制粘贴基本相同,但有严格的约定。此外,我必须输入 foobar 总共 6 次,才能根据界面正确分配默认 optional 值。 p>

还有更有效的方法吗?

编辑;我正在努力实现以下目标:

具有属性的类,其中的属性可用于 typescript 的检查,如下所示: 界面

export default interface FooDTO {
  foo: string;
  bar: number;
}

模型

export interface IFoo {
  foo: string;
  bar: number;
}

export default class FooModel implements IFoo {
  foo: string;
  bar: number;

  constructor({ foo, bar }: IFoo = { foo: 'hello', bar: 1 }) {
    this.foo = foo;
  }
}

控制器

export default class FooController {
   public static async readAll(): Array<FooDTO> {
      // some model stuff which maps query to dto
      return Array<FooDTO>result;
   }
}

【问题讨论】:

  • 为什么要在接口上定义属性?
  • @Jenny O'Reilly 所以我可以将它们用于类型分配,假设我使用function findById(id: number): Array&lt;IFoo&gt; { ... some fetching here ...; return Array&lt;IFoo&gt;results; }。它更多的是为了一致性/明确的可读性和 IDE infellisense
  • 好的。没关系。但是为什么你还有一个实现它的类呢?
  • @JennyO'Reilly 所以我可以创建一个新实例来创建模型,我希望它具有与接口相同的属性并分配它,例如:const foo: FooModel = new FooModel({foo: "hi"});这只会在新实例上操作 foo 而不是 bar,因为 bar 有一个默认值。
  • @JennyO'Reilly 我可能确实会提出一个非常明确的新问题,我可能会稍后再做

标签: typescript class implements


【解决方案1】:

我认为“为什么我必须重新实现相同的属性”的规范答案在(越来越过时的)TypeScript spec

请注意,由于 TypeScript 具有结构化类型系统,因此类不需要显式声明它实现了一个接口——类只需包含适当的实例成员集就足够了。类的implements 子句提供了一种机制来断言和验证该类是否包含适当的实例成员集,否则它对类类型没有影响

我在上面添加了重点:implements 子句根本不影响类类型;它不会向其中添加成员或更改成员的类型。它所做的只是告诉编译器在类不符合接口时发出警告。


您可能对 GitHub 问题 microsoft/TypeScript#22815 感兴趣,该问题建议应将已实现接口的成员复制到实现类中。 (问题的标题是关于抽象类的,但随后的讨论不限于此。)看起来症结在于如何处理可选成员(参见 TS 团队负责人的this comment)。这个问题是一个较旧的问题,但它仍然以“需要提案”标签打开,所以如果你非常关心它,你可能想去那里,给它一个?,甚至可能提供更多关于行为应该如何的细节处于边缘情况,以免造成重大变化。

在这个问题里面是一个建议的workaround 使用interface merging 告诉编译器类实例接口继承属性:

interface FooModel extends IFoo { } // merge into FooModel
class FooModel {
    constructor({ foo, bar }: IFoo = { foo: 'hello', bar: 1 }) {
        this.foo = foo;
        this.bar = bar;
    }
}

这不是多余的,但可能比仅仅从接口重新声明属性更令人困惑。


无论如何,希望对您有所帮助;祝你好运!

Playground link to code

【讨论】:

    【解决方案2】:

    您可以使用abstract class 代替接口。与接口相同的好处,但可以扩展它:

    export abstract class IFoo {
      foo: string;
      bar: number;
    }
    
    export default class FooModel extends IFoo {
      constructor({ foo, bar }: IFoo = { foo: 'hello', bar: 1 }) {
        this.foo = foo;
      }
    }
    

    【讨论】:

    • 这样的优缺点是什么?如果将类作为接口看起来有点奇怪
    • 没有缺点,您可以将其用作interface,也可以使用implements,但是您必须像以前一样定义字段。这只是打字稿的好东西
    • 另外,当使用一个抽象类时,只要你不扩展它,它就不会被包含在emit中,所以它会节省字节
    • 这听起来很有趣,这是否像接口一样与类型转换一起使用?喜欢:const NewFoo: IFoo = { foo: "hi", bar: 2 };
    • @Nurfey 绝对没有问题 Playground Link
    【解决方案3】:

    如果你定义一个只有属性的接口,你基本上定义了一个数据结构类型,然后你可以用它来输入对象。

    例如,您现在可以这样做:

    const fooObj: IFoo = { foo: "test", bar: 12 };
    

    在其他编程语言(例如 Java)中,在接口上定义属性是非法的。接口“通常”定义了您可以与对象交互的方式(意味着您可以在其上调用的方法)。但 TypeScript 是个例外。

    【讨论】:

    • 我确实将它用于数据结构和 DTO,这就是我需要它的原因,但我认为我可能不应该将接口用于 DTO,但它工作得很好,而且它使映射变得容易跨度>
    • @Nurfey 我明白了。但是实现一个只有属性的接口对我来说毫无意义。由于接口就像类必须实现的“合同”。如果没有方法,也就没有契约。
    • 有道理,有没有其他方法可以使用属性自动完成进行类型转换..?
    • @Nurfey 我不确定我是否理解您想要实现的目标。也许您可以使用更多信息来更新您的问题?不仅是您遇到的问题,还有您想要实现的目标。
    • @Nurfey 感谢您的反馈。在这种情况下,通常不包括类型,因为它会干扰可读性并且没有必要。所以const foo = new FooModel(); 就足够了。就像const num = 5; 就够了,不用写const num: number = 5;
    猜你喜欢
    • 2023-04-04
    • 2012-10-26
    • 2017-08-06
    • 2020-02-11
    • 2018-08-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多