【问题标题】:Interface and class in TypeScriptTypeScript 中的接口和类
【发布时间】:2017-04-09 18:12:09
【问题描述】:

在 C# 中,接口和类之间存在巨大差异。事实上,一个类代表一个引用类型,因此我们实际上可以创建以该类为模型的对象,而接口意味着一个类签署的契约,以确保特定行为的存在。特别是我们不能创建接口的实例。

接口的全部意义在于暴露行为。一个类通过给出所述行为的一个显式实现来实现它。

在这种情况下,虽然接口可能包含属性,但大多数时候我们关心接口是因为行为问题。所以大多数类型,接口只是行为契约。

另一方面,在 TypeScript 上,我似乎有一些让我很不安的东西,实际上我不止一次看到过这种情况,这就是提出这个问题的原因。

在一个教程中我看到了这个:

export interface User {
    name: string; // required with minimum 5 chracters
    address?: {
        street?: string; // required
        postcode?: string;
    }
}

但是等一下。为什么User是一个接口?如果我们像 C# 一样思考,User 不应该是一个接口。事实上,看起来我们似乎在定义数据类型User,而不是行为契约。

像我们在 C# 中那样思考,自然会是这样:

export class User {
    public name: string;
    public address: Address;
}
export class Address {
    public street: string;
    public postcode: string;
}

但是,像我们使用类一样使用接口来定义数据类型,而不是定义行为契约,这在 TypeScript 中似乎很常见

那么 TypeScript 中的接口是什么意思?为什么人们在 TypeScript 中使用接口,就像我们在 C# 中使用类一样?在 TypeScript 中应该如何正确使用接口:建立行为契约,或者定义属性和对象应该具有的?

【问题讨论】:

  • 很少来自 C# 的内容适用于 TypeScript,因为后者是 structurally typed。此外,拥有某些属性绝对可以被视为一种行为:-)
  • 我不知道你期待什么样的答案。你不能像那样比较语言。
  • 我认为引用 C# 是错误的。我不想比较语言,也不想比较任何东西。我刚刚谈到 C# 作为我习惯使用的接口的一个例子。我想知道的是我们什么时候应该在 TypeScript 中使用接口或类,因为人们似乎将它们都用于相同的目的,而不是为它们每个都有一个特定的目的。正如我所说,同样可以使用类和接口来完成,而且我似乎有人在做这两种方式,这让我感到困惑。问题是只是关于打字稿,C# 只是一个例子。
  • 好的,但是 TypeScript 中的接口用于类似目的。并且您不能互换使用 then 。例如,你不能在给定接口IUser 的情况下使用new IUser(),所以从我的角度来看,如果你能更具体地说明什么让你感到困惑,那就太好了。是“嵌套内联”接口还是可选属性?

标签: javascript typescript interface


【解决方案1】:

考虑到在 Javascript 中,数据通常作为普通对象交换,通常通过 JSON:

let data = JSON.parse(someString);

假设dataUser 对象的数组,我们将把它传递给一个函数:

data.forEach(user => foo(user))

foo 会这样输入:

function foo(user: User) { ... }

但是等等,我们从来没有做过new User!我们应该吗?我们是否必须编写一个类Usermap 所有data 给它,即使结果完全一样,一个带有属性的Object?不,这只是为了满足类型系统而疯狂,但不会改变运行时的任何内容。一个简单的interface 描述了特定对象的预期外观(“行为”)就足够了。

【讨论】:

  • 是的,data.forEach(foo)...为了清楚起见,我只是使用速记。 :o)
  • “仅仅为了满足类型系统,这将是疯狂的,但不会改变运行时的任何内容。” - 这条线对我产生了影响,谢谢 :) 并且......我说得对吗,这么多类型系统会失去底层 javascript 的活力?
  • @ASolvent 动态性是一把双刃剑……使用简单接口的鸭子类型对象很棒,但是通过上下文隐式转换简单类型可能是一个陷阱。你想找到一个快乐的媒介:在必要的地方确定类型,在足够的地方确定类型。
  • 此方法仅在您 100% 信任数据源或格式错误的数据不会导致问题的情况下才足够。
【解决方案2】:

我也是从 C# 背景来到 Typescript 的,并且想知道同样的事情。我在按照 POCO 的思路思考(POTO 是一件事吗?)

那么 TypeScript 中的接口是什么意思?

The Typescript Handbook 似乎在说接口是为了“在你的代码中定义合约”。

为什么人们在 TypeScript 中使用接口,就像我们在 C# 中使用类一样?

我同意@deceze 在这里的回答。

John Papa 在他的blog 上扩展了类和接口的主题。他建议类最适合“使用继承和单例对象创建多个新实例”。因此,根据 Typescript Handbook 中描述的 Typescript 接口的意图和一个人的意见,似乎不需要在 Typescript 中建立类。相反,您应该使用接口。 (你的 C# 感觉仍然会被冒犯。)

接口应该在 TypeScript 中正确使用:建立行为契约,还是定义对象应该具有的属性?

如果我理解这个问题,您是在问接口是否应该建立行为合同或结构合同>。对此,我会回答:两者都有。 Typescript 接口仍然可以像在 C# 或 Java 中使用接口一样使用(即描述类的行为),但它们也提供了描述数据结构的能力。

此外,我的同事指责我使用类而不是接口,因为接口在编译器中不会产生任何代码。

例子:

这个打字稿:

class Car implements ICar {
    foo: string;
    bar(): void {
    }
}

interface ICar {
    foo: string;
    bar(): void;
}

产生这个 Javascript:

var Car = (function () {
    function Car() {
    }
    Car.prototype.bar = function () {
    };
    return Car;
}());

Try it out

【讨论】:

    【解决方案3】:

    typescript 中的接口类似于 C# 中的接口,因为它们都提供了契约。然而,与仅包含方法的 C# 接口相反,typescript 接口也可以描述对象包含的字段或属性。因此,它们也可以用于 C# 接口无法直接实现的事情。

    typescript 中接口和类之间的一个主要区别是接口没有运行时表示,也不会为它们发出任何代码。接口的用途非常广泛。例如,您可以使用对象文字来构造满足接口的对象。喜欢:

    let user: User = {
      name: 'abc',
      address: {
        street: 'xyz',
      },
    };
    

    或者您可以将任何数据对象(例如通过 JSON 解析接收)分配给接口(但您的预检查应该断言它是真正有效的数据)。因此接口对于数据来说非常灵活。

    另一方面,类在运行时具有与它们关联的类型,并且会生成代码。您可以在运行时使用instanceof 检查类型,并且设置了原型链。如果您将User 定义为一个类,除非您调用构造函数,否则它将不是有效用户。而且您不能将任何合适的数据定义为User。您需要创建一个新实例并复制属性。

    我个人的经验法则:

    • 如果我正在处理(来自不同来源的)纯数据,我会使用接口
    • 如果我正在建模具有身份和状态的东西(可能还附加了修改状态的方法),我正在使用一个类。

    【讨论】:

    • 很好的答案,尤其是您的经验法则。发现它们很有帮助。谢谢!
    • 很好的答案 - 除了最后你说“可能还有方法” - 但是如果你使用这样的类通过 JSON.stringify() 将数据发送到 API,你最终会得到一个空对象{} 因为它不知道如何处理这些方法。请参阅下面来自@JasonKleban 的答案
    【解决方案4】:

    在 TypeScript 中应该如何正确使用接口:建立行为契约,或者定义对象应该具有的属性?

    TypeScript 中的接口是形状契约,描述了对象的预期结构。如果一个值具有特定的接口注释,您希望它是一个具有接口中定义的成员的对象。成员可以是值或函数(方法)。一般来说,他们的行为(功能体)不是合同的一部分。但是您可以指定它们是否为readonly

    那么 TypeScript 中的接口是什么意思?为什么人们在 TypeScript 中使用接口,就像我们在 C# 中使用类一样?

    Typescript 接口如果期望由 TypeScript 类实现,则可以起到与 C# 接口相同的作用。

    但是不仅一个类可以实现一个接口;任何类型的值都可以:

    interface HelloPrinter {
        printHello(): void
    }
    

    以下对象不是类,但实现了接口:

    {
        printHello: () => console.log("hello")
    }
    

    我们可以这样做

    const o: HelloPrinter = {
        printHello: () => console.log("hello")
    }
    

    TypeScript 编译器不会抱怨。

    该对象实现了我们的接口,而不强制我们编写一个类。

    使用接口比使用(接口和)类更轻便。

    但是如果您需要在运行时知道类型名称(类/接口名称),那么类是正确的选择,因为接口名称仅在编译时才知道。

    【讨论】:

      【解决方案5】:

      仅使用本机反序列化机制,您无法反序列化特定类的实例。您只能反序列化为普通的旧 javascript 对象。此类对象可以遵循 typescript 接口,但不能是类的实例。如果您需要处理跨越序列化边界的数据,例如来自 Web 服务的预期数据,请使用接口。如果您需要自己生成此类值的新实例,只需按字面意思构造它们或创建一个返回它们的便利函数 - 遵循该接口的对象。

      一个类本身可以实现一个接口,但如果您希望同时处理本地构造的类实例和反序列化、重组的普通对象,它可能会让人感到困惑。您永远无法依赖对象的类基础,因此将其定义为用于该确切目的的类也没有任何好处。

      我已经成功创建了一个 ServerProxy 模块,该模块负责从 web 服务来回发送代码 - web 服务调用和返回的结果。如果您要绑定到淘汰模型或类似模型,您可以拥有一个封装 ui 绑定模型的类,该类使用一个构造函数,该构造函数知道如何将遵循 web 服务的仅接口合同的返回的普通旧 JavaScript 对象提升到你的模型类的一个实例。

      【讨论】:

        猜你喜欢
        • 2017-04-19
        • 1970-01-01
        • 2018-09-15
        • 1970-01-01
        • 1970-01-01
        • 2015-04-01
        • 2015-02-20
        • 2016-02-03
        相关资源
        最近更新 更多