添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接
相关文章推荐
知识渊博的柚子  ·  com.lagradost.cloudstr ...·  38 分钟前    · 
稳重的皮带  ·  QObject::connect: ...·  2 小时前    · 
好帅的西装  ·  Session (Jakarta ...·  3 小时前    · 
刚毅的火车  ·  Session (Java(TM) EE ...·  3 小时前    · 
害羞的小熊猫  ·  Bug ID: JDK-8048050 ...·  4 周前    · 
老实的毛衣  ·  齐白石弟子徒弟一览表·  5 月前    · 
有腹肌的李子  ·  2024 - 10 min. to ...·  8 月前    · 

In one of my recent PRs I changed all interface s to type s because there were already more type s than interface s. In the review, I was asked to revert the change. I did it, but as well I wondered what the actual difference between interface and type . Let's figure out this. I use the latest TS (v3.5.1) for examples in this post.

interface IRoboAnimal1 extends IAnimal , IRobot {} interface IRoboAnimal2 extends IAnimal , Robot {} interface IRoboAnimal3 extends Animal , IRobot {} interface IRoboAnimal4 extends Animal , Robot {} type RoboAnimal1 = Animal & Robot ; type RoboAnimal2 = Animal & IRobot ; type RoboAnimal3 = IAnimal & Robot ; type RoboAnimal4 = IAnimal & IRobot ; Enter fullscreen mode Exit fullscreen mode
type Close = { a: string };
const x: Close = { a: "a", b: "b", c: "c" };
// Type '{ a: string; b: string; c: string; }' is not assignable to type 'Close'.
interface IClose {
  a: string;
const y: IClose = { a: "a", b: "b", c: "c" };
// Type '{ a: string; b: string; c: string; }' is not assignable to type 'IClose'.
    Enter fullscreen mode
    Exit fullscreen mode
interface INewNumber extends number {}
// 'number' only refers to a type, but is being used as a value here.
// this works
interface INewNumber extends Number {}
// but don't forget that 1 instanceof Number === false;
    Enter fullscreen mode
    Exit fullscreen mode
[1, 2, 3] as Tuple; // Conversion of type '[number, number, number]' to type '[number, number]' may be a mistake
[1, 2, 3] as ITuple; // Ok
    Enter fullscreen mode
    Exit fullscreen mode
class Parrot implements IClassyAnimal {
  name: string;
  constructor(name: string) {
    this.name = name;
// Class 'Parrot' incorrectly implements interface 'IClassyAnimal'.
//  Type 'Parrot' provides no match for the signature 'new (name: string): void'.
    Enter fullscreen mode
    Exit fullscreen mode
class Parrot implements IClassyAnimal {
  name: string;
  constructor(name: string) {
    this.name = name;
// Class 'Parrot' incorrectly implements interface 'IClassyAnimal'.
//  Types of property 'constructor' are incompatible.
//    Type 'Function' is not assignable to type '(name: string) => void'.
//      Type 'Function' provides no match for the signature '(name: string): void'.
    Enter fullscreen mode
    Exit fullscreen mode

Not all of those things were possible in early versions of TS, so people got used to interfaces. But in the latest version of TS, it seems that types are more capable and we can always use them 🤔. Or I miss something?

There are a lot of nuances in TS - something may work for a small example (which I showed), but broken for big ones. Please correct me if I missed something.

Dedicated to @thekitze.

One of the sudden realizations I had about types and interfaces in TypeScript is that they're not actually "types", they're type aliases. As they don't define new types, they just they give them a new name.

In other words, they could have used the keyword alias instead of type and maybe saved some troubles.

This means that interfaces are "opaque" relatively to its internal structure, whereas type aliases are not. I.e., the type hint you get is just interface IAnimal in the former case, and the whole type alias definition in the latter.

function petFactory(petClass: IClassyAnimal, name: string) { return new petClass(name); const pet = petFactory(Parrot, 'McParrotface'); // this is fine Enter fullscreen mode Exit fullscreen mode const petClass: IClassyAnimal = class Parrot { static group = 'Bird'; constructor(public name: string) {} Enter fullscreen mode Exit fullscreen mode

On the other hand, when you're using implements in a class, you're describing the shape of an instance of that class. Which means you write class Parrot implements IAnimal {...}, because Parrot instances comply to the shape defined by IAnimal.

Edit: missed one of your replies in the comments that is actually on point on that 🙂

the type hint you get is just interface IAnimal in the former case, and the whole type alias definition in the latter.

this is interesting. I saw it somewhere before but didn't pay attention, now I see what this was about.

A computed property name in an interface must refer to an expression whose type is a literal type or a 'unique symbol' type. A computed property name must be of type 'string', 'number', 'symbol', or 'any'. 'TAnimal' only refers to a type, but is being used as a value here. interface IAnimals { [a in TAnimal]: string // This works. type TAnimals = { [a in TAnimal]: string
let suits = ["hearts", "spades", "clubs", "diamonds"];
function pickCard(x: {suit: string; card: number; }[]): number;
function pickCard(x: number): {suit: string; card: number; };
function pickCard(x: any): any {
    // Check to see if we're working with an object/array
    // if so, they gave us the deck and we'll pick the card
    if (typeof x == "object") {
        let pickedCard = Math.floor(Math.random() * x.length);
        return pickedCard;
    // Otherwise just let them pick the card
    else if (typeof x == "number") {
        let pickedSuit = Math.floor(x / 13);
        return { suit: suits[pickedSuit], card: x % 13 };
interface IPickCard {
    (x: {suit: string; card: number; }[]): number
    (x: number): {suit: string; card: number; }
type PickCard = 
  | ((x: {suit: string; card: number; }[]) => number)
  | ((x: number) => {suit: string; card: number; });
const y: PickCard = pickCard;
const x: IPickCard = pickCard;

There’s an example of the usefulness of this in a older than the current version of a functional programming library fp-ts.
It’s called declaration merging and it was used in that library, that implements higher kinded types, to extend those types.

An example where you'd want to extend the interface of a 3rd party API is OrderCloud. They have the property xp to enable you to extend their data model. Here's an example of a type where xp is object (I think sometimes it's any too).
And the extension would be something like:

interface OrderExtended extends Order {
    xp: OrderXp | null
export interface OrderXp {
  isCool: boolean
  isAwesome: boolean
  maybeIsBoth: boolean

From TS docs:

“As we mentioned, type aliases can act sort of like interfaces; however, there are some subtle differences.

One difference is that interfaces create a new name that is used everywhere. Type aliases don’t create a new name — for instance, error messages won’t use the alias name. In the code below, hovering over interfaced in an editor will show that it returns an Interface, but will show that aliased returns object literal type.“

This is why we prefer interfaces. It makes everything more readable.

"=" is already odd, different from classes and all other non-object "{ ... }" statements.
Also, almost no non-advanced training material I can remember talks about "type"..
Running a Dev shop, why overload people's brains with something that looks archaic and has questionable use (at least for business applications)?
Also everyone has lots of historic interface/class/type/structure luggage from other languages that doesn't map into using types in place of interfaces.
I imagine there's valid use for it for dev tool creators.

a) What is your reason behind it?
b) Why archaic is a bad thing? Math notation of plus is archaic, let's change the notation?

I feel like people who have background in Java, C# would be more comfortable with notation of interface.
People who have background in functional languages, like OCaml (all ML family?) will prefer type notation.
People who don't have background - will accept whatever you show them first.

Argument "=" is already odd is a matter of taste. And if you don't like it based on that, there is no reason for me to argue. There are people who prefer to put semicolons in the end of JS and who don't...

I also disagree with this, I think for one an interface in the OO world conveys the message that somewhere you're going to have an implementation of sorts and when you don't have at least one of those that is not being explicit.

I would also argue that an interface conveys the message of abstraction which is not the case when one would use it to define the structure of an object which a type does a much better of.

I think a type is very explicit in that sense.

Also, you say that type is archaic, well, it's not, it is used in many other languages (modern languages) to represent exactly that.

I had a similar experience, which led me to open a PR for the Typescript handbook, documenting using type aliases over interfaces.

github.com/microsoft/TypeScript-Ha...

class Parrot implements IClassyAnimal { name: string; constructor(name: string) { this.name = name; // Class 'Parrot' incorrectly implements interface 'IClassyAnimal'. // Type 'Parrot' provides no match for the signature 'new (name: string): this'. class MadeFromString implements ComesFromString { constructor (public name: string) { console.log('ctor invoked'); function makeObj(n: StringConstructable) { return new n('hello!'); class Parrot implements IClassyAnimal { name: string; constructor(name: string) { this.name = name;

Yes. I wasn't unable to check it, but I was sure I did something like that. But according to the docs

typescriptlang.org/docs/handbook/i...

Seems like you are right. Constructors interfaces declarations are mostly effective as function arguments.

Built on Forem — the open source software that powers DEV and other inclusive communities.

Made with love and Ruby on Rails. DEV Community © 2016 - 2024.