Use which: Interface or Type Alias in Typescript?

magentaqin

qinmu

Posted on January 4, 2023

Use which: Interface or Type Alias in Typescript?

This blog mainly discusses the key differences of interface vs type, and share how I would use them in my daily software development routine.

Describe the shape of an object or a function signature

Both can be used to describe the shape of an object or a function signature.

However, I mostly use interface to describe the object, and use type to describe a function signature, as I can clearly know it’s a definition for function from arrow function, which is impossible to realize in interface.

interface Point {
  x: number;
  y: number;
}

type SetPoint = (x: number, y: number) => void;
Enter fullscreen mode Exit fullscreen mode

Another scenario where I use interface is for 「Index Signatures」. For example, in dynamic generated input, we don’t know all the names of a type’s properties ahead of time, but we do know the shape of the values.

interface Input {
  [index: string]: string | number;
}
Enter fullscreen mode Exit fullscreen mode

Extending and Intersection Types

At first sight, extending and intersection are similar ways to combine types. However, the principle difference between the two is how conflicts are handled.

For interface, If you declare a property with the same key, it will overrides the one in the derived interface. However, it should be compatible with the previous one, or typescript compiler will throw an error.

// This works pretty well, as it is compatible
interface Converter {
  convert: (value: number) => string;
}

interface StringNumberConverter extends Converter {
  convert: (value: number | string) => string;
}

// Oops...this will throw an error, as it is incompatible
interface Converter {
  convert: (value: number) => string;
}

interface StringNumberConverter extends Converter {
  convert: (value: string) => string;
}
Enter fullscreen mode Exit fullscreen mode

Image description
For type, properties with the same key don’t have to be compatible. You can declare whatever you like.

type Converter = {
  convert: (value: number) => string;
}

type StringNumberConverter = Converter & {
  convert: (value: string) => string;
}
Enter fullscreen mode Exit fullscreen mode

Another difference you should pay attention is the meaning they represent. When I use interface, I want it stands for the「Parent-Child」relationship. By contrast, when I use type, it only stands for 「Composition」relationship.

// 「Parent-Child」relationship. Cat derives from Animal.
interface Animal {
  name: string;
}

interface Cat extends Animal {
  color: string;
}

// 「Composition」relationship. Color and Size are two seperate species
type Color = {
  name: string
}
type Size = {
  size: 'small' | 'medium' | 'large'
}

type ColorSize = Color & Size
Enter fullscreen mode Exit fullscreen mode

Class Implements

A class can implement an interface or type alias, but it can not implement Union type, as in typescript, a class can only implement an object type or intersection of object types with statically known members.

So in daily development, if this type is designed to be implemented by class, I will use interface over type.

type PartialPoint = { x: number; } | { y: number; };

// can not implement a union type
class SomePartialPoint implements PartialPoint {
  x = 1;
  y = 2;
}
Enter fullscreen mode Exit fullscreen mode

More than Object Types

Unlike an interface, type alias can also be used for other types. Frequently used are such as union type and tuples.

type StringNumberPair = [string, number];
function doSomething(stringHash: [string, number]) {
  const [inputString, hash] = stringHash;
  console.log(inputString); // string
  console.log(hash);  // number        
}
Enter fullscreen mode Exit fullscreen mode

Declaration Merging

interface is open, and type alias is close within itself. An interface can be defined multiple times, and will be treated as a single interface with members of all declarations being merged.

type Bear = {
  name: string;
}
// Error:Duplicate Identifier
type Bear = {
  age: number;
}

// works pretty well
interface Bear {
  name: string;
}

interface Bear {
  age: number;
}

const b: Bear = {
  name: '',
  age: 0
}
Enter fullscreen mode Exit fullscreen mode

Working With typeof Type Operator

In most cases, if you want to work with typeof type operator, choose type over interface.

function f() {
  return { x: 10, y: 3 };
}

type P = ReturnType<typeof f>;

/**
** type P = {
    x: number;
    y: number;
}
**/
Enter fullscreen mode Exit fullscreen mode

Conclusion

When the next time you are confused which to use, try this cheatsheet!

Image description

💖 💪 🙅 🚩
magentaqin
qinmu

Posted on January 4, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related