Interface vs Type alias in TypeScript 2.7
People often ask me ( online, at work, in skatepark (nope 😂) ), what’s the difference between using type
and interface
for defining compile time types within TypeScript.
First thing I used to do, was pointing them to TypeScript handbook…
Unfortunately most of the time, they didn’t find the droids that they were looking for ( it’s hidden within Advanced Types section). Even if they found it, the information described there is obsolete ( described behaviour is for typescript@2.0.x ).
Good news everyone! You don’t have to look any further, This post is an up to date description/style-guide about when to use interface
vs type
alias.
What official documentation says:
“type aliases can act sort of like interfaces, however, there are some subtle differences.”
that’s correct !
What differences?
1. “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.”
that’s incorrect ! (since TypeScript 2.1)
Let’s define compile time types for Point
via interface and type alias and 2 implementation of getRectangleSquare
function which will use both interface and type alias for parameter type annotation.
So errors are same for both:
// TS Error: // Interface:Argument of type '{ x: number; }' is not assignable to parameter of type 'PointInterface'. Property 'y' is missing in type '{ x: number; }'.// Type alias:Argument of type '{ x: number; }' is not assignable to parameter of type 'PointType'. Property 'y' is missing in type '{ x: number; }'.
2. “A second more important difference is that type aliases cannot be extended or implemented from”
Again, that’s incorrect!
We can extend an interface with type alias:
Or use type alias for implementing a Class constraint
Or use interface extended by an type for implementing a Class constraint
We can also combine both type alias and interface for implementing a Class constraint
3. “type aliases cannot extend/implement other types”
Again, that’s incorrect!
Well it’s partially correct but the formulation is miss leading 👀.
You can use interface or any other TypeScript valid type(which has shape of an Dictionary/JS Object, so non primitive types etc…) for type alias extension via intersection operator &
We can also leverage mapped types for various transforms of both interface and type alias.
Let’s make Shape and Perimeter optional via Partial
mapped type:
Also weak type detection works correctly:
Hybrid Types with both type alias and interface
You might occasionally want to define an object that that acts as both a function and an object, with additional properties.
What we are talking here about, is defining a type for a function ( callable object ) and static properties on that function.
This pattern might be also seen, when interacting with 3rd-party JavaScript, to fully describe the shape of the type.
It works equally with type alias!
There is a very subtle difference though. You will get the particular shape type in IDE instead of reference to the Counter
type.
What is usually a good idea/practice, is to dissect our hybrid definition in two parts:
- callable object (function) type alias
- static properties object shape
and final Counter
type:
So what’s the difference between type alias and interface again 🤖?
1. you cannot use implements
on an class with type alias if you use union
operator within your type definition
This will trigger compile errors:
Which makes complete sense! A class blueprint, cannot implement one or another shape structure, so nothing surprising on this front.
Where type alias union usage makes sense and also works, is for object definition via object literal. So following is valid and will produce compile error, because our object has to define one of perimeter()
or area()
methods, or both:
2. you cannot use extends
on an interface with type alias if you use union
operator within your type definition
Again, similarly to class implements
usage, interface is a "static" blueprint — it cannot exists in one or another shape, so it cannot be extended
by union type merge.
3. declaration merging doesn’t work with type alias
While declaration merging works with interfaces, it fails short with type aliases.
What I mean by declaration mergin?:
You can define same interface multiple times, and its definitions will merge into one:
This doesn’t work with type aliases, because type is an unique type entity ( for both global or module scope ):
Declaration merging via interfaces is very important, when we are writing 3rd party ambient type definitions for libraries that are not authored with TypeScript, so consumer has option to extend them, if some definition are missing.
Same applies if our library is written in TypeScript and ambient type definitions are generated automatically.
This is the only use case, where you definitely should always use interface instead of type alias !
⚛️: What should I use for React Props
and State ?
In general, use what you want ( type alias / interface ) just be consistent, but personally, I recommend to use type aliases:
- it’s shorter to write
type Props = {}
- your syntax is consistent ( you are not mixin interfaces with type aliases for possible type intersections )
// BAD
interface Props extends OwnProps, InjectedProps, StoreProps {}
type OwnProps = {...}
type StoreProps = {...}// GOOD
type Props = OwnProps & InjectedProps & StoreProps
type OwnProps = {...}
type StoreProps = {...}
- your public component Props/State implementation cannot be monkey patched and for that reason, consumer of your component should never need to leverage interface declaration merging. For extension there are clearly defined patterns like HOC and so on.
Summary
In this article we learned what is the difference between interface
and type
alias in latest TypeScript.
With that covered, we came to an conclusion what method of defining compile time types should be used in a particular scenario.
Let’s recap:
- type aliases can act sort of like interfaces, however, there are 3 important differences ( union types, declaration merging)
- use whatever suites you and your team, just be consistent
- always use
interface
for public API's definition when authoring a library or 3rd party ambient type definitions - consider using
type
for your React Component Props and State
As always, don’t hesitate to ping me if you have any questions here or on twitter (my handle @martin_hotell) and besides that, happy type checking folks and ‘till next time! Cheers!