admin管理员组

文章数量:1122846

In my understanding, this is how you define interface inside class:

class A {
  a: number
  constructor() {
    this.a = 0
  }
}

And this is how you define interface outside class:

interface A {
  a: number
}
class A {
  constructor() {
    this.a = 0
  }
}

What is the difference between the two cases? When should I use one over the other? I notice that private and protected modifiers can't be used in interface, so if I need to use them then of course I need to use the inside case. But if I only use public, then it seems to be no difference between them?

Reading the documentation for Classes and Everyday Types doesn't help.

In my understanding, this is how you define interface inside class:

class A {
  a: number
  constructor() {
    this.a = 0
  }
}

And this is how you define interface outside class:

interface A {
  a: number
}
class A {
  constructor() {
    this.a = 0
  }
}

What is the difference between the two cases? When should I use one over the other? I notice that private and protected modifiers can't be used in interface, so if I need to use them then of course I need to use the inside case. But if I only use public, then it seems to be no difference between them?

Reading the documentation for Classes and Everyday Types doesn't help.

Share Improve this question edited Nov 24, 2024 at 13:32 Ooker asked Nov 22, 2024 at 14:44 OokerOoker 2,9105 gold badges38 silver badges79 bronze badges 10
  • 1 interface defines a public interface, allowing other modifiers would make no sense - stackoverflow.com/q/32888353/3001761. – jonrsharpe Commented Nov 22, 2024 at 14:51
  • if you don't export it, can it still be public? – Ooker Commented Nov 22, 2024 at 14:58
  • 1 The latter form is declaration merging. You've re-opened the class instance type and added the interface stuff to it. There are observable differences; stuff merged in won't be checked the same way. (e.g. with --useDefineForClassFields then there will be no Object.defineProperty() for a merged field; you can't assign readonly merged fields in the ctor, etc). You can certainly use readonly, but private doesn't work on an interface in general. Does this fully address the q? If so I'll write an a. If not, what's missing? – jcalz Commented Nov 22, 2024 at 19:05
  • @jcalz I understand that this is declaration merging, but I don't get why a consequence of this is you can't use modifiers. Certainly I can only define an interface just for internal use, right? – Ooker Commented Nov 23, 2024 at 3:27
  • You say “modifiers” but you certainly can use readonly and optional modifiers. If you want to know why protected or private aren’t supported in interface declarations that’s a fair question, I suppose, but can you please edit to clarify and distinguish between the modifiers because they are not the same thing. – jcalz Commented Nov 23, 2024 at 4:25
 |  Show 5 more comments

1 Answer 1

Reset to default 2

What you're calling "defining an interface inside a class" is just the normal way you write classes. Fields generally are declared in the class:

class A {
  a: number
  constructor() {
    this.a = 0
  }
}

If you compile the above to target modern JavaScript and use the appropriate compiler options, it will produce a JavaScript class with a public class field:

// JavaScript
class A {
    a; // <-- public class field declaration
    constructor() {
        this.a = 0;
    }
}

On the other hand, what you're calling "defining an interface outside a class" is known as declaration merging. The interface merges with the class instance type, which sort of "patches" it at the type level. Interface declarations have absolutely no runtime effect, so anything you put in there that normally goes into the class declaration might behave differently. For example:

interface B {
  a: number
}
class B {
  constructor() {
    this.a = 0
  }
}

If you compile that with the same compiler settings as above, you will get the following JavaScript, without a class field declaration:

// JavaScript
class B {
    // <-- no public class field declaration
    constructor() {
        this.a = 0;
    }
}

The fact that the two approaches can result in different JavaScript is an indication that they are not identical, although there's little pragmatic difference in this example. Still, you could easily change things where there are noticeable differences:

class C { a?: number }
console.log(Object.keys(new C()).length) // 1

interface D { a?: number }
class D { }
console.log(Object.keys(new D()).length) // 0

And certainly there are type checking differences. Interface merging adds stuff without having to pass the normal sorts of type checks:

class E { a: number } // compiler error
//        ~
// Property 'a' has no initializer and is not definitely 
// assigned in the constructor.
new E().a.toFixed(); // runtime error

interface F { a: number }
class F { } // no compiler error
new F().a.toFixed(); // runtime error

In the above, E gives you a compiler error that you forgot to initialize a, whereas F does not give you that error, even though the same problem exists. So E warns you about an issue that F doesn't notice.


Also, interfaces are not class declarations, so many class-specific things you can normally do are not available in interface merging. The most obvious example is that classes allow you to write runtime code, whereas interfaces do not. You can initialize class fields and implement class methods, but "initializing" an interface property or "implementing" an interface method doesn't make much sense:

class G { a: number = 1; b(): number { return 2 } };

interface H { a: number = 1, b(): number { return 2 }} // errors!

Any class-specific modifiers like private or protected or static or abstract are unavailable in interfaces:

abstract class I {
  abstract a: number;
  private b?: number;
  static c: number;
}

interface J {
  abstract a: number; // error!
  private b?: number; // error!
  static c: number; // error!
}

As for what you should do, this is at least somewhat subjective. In my opinion, declaration merging is an advanced technique that you only use to work around limitations in existing code. If you can do something without using it, you should.

Playground link to code

本文标签: typescriptWhat is the difference between defining interface outside and inside a classStack Overflow