Interface と Type の比較

interfacetypeもどちらも新しい型を定義する為に使います。以下はFooという型を「ほぼ」同じ結果になるように実装したコードです。

interface Foo {
  value: string;
}

const foo: Foo = {
  value: 'foo'
};
type Foo = {
  value: string;
};

const foo: Foo = {
  value: 'foo'
};

interfaceの方は、class宣言やfunction宣言のようにな形を取ります。一方で、type=を使ってconstなどのように値の定義のような形を取ります。

TypeScript のバージョンが2だった時ぐらいまでは色々と違いがありましたが、3が主流となった今(2020-04-19)、ほぼ違いは無くなりました。ただ若っ干の違いはまだ残っているので、同じなのも含めてざっと見ていきます。

関数

interfaceの方が分かりづらいですが、関数もどちらでも宣言・定義できます。

interface A {
  (num: number): number;
}
type B = (num: number) => number;

const a: A = (num) => num;
const b: B = (num) => num;

インデックス署名

どちらも同じです。

interface A {
  [key: string]: string;
}

type B = { /* 同じ */ };

const a: A = { /* ... */ };

a['key'];

Generic

どちらも名前の後に Generic パラメーターが置けます。

interface A<Value> {
  value: Value;
}

type B<Value> = { /* 同じ */ }

implements

どちらもimplementsとして指定できます。

interface A { /* ... */ }
type B = { /* ... */ }

class Foo implements A, B {}

ただし Union なtypeは使えませんが、どちらかなんてハッキリしない型を使えないイメージは何となく想像しやすいと思います。

type X = { a: unknown} | { b: unknown };
// A class can only implement an object type or intersection of object types with statically known members.
class Foo implements X {}

コンストラクタ署名

どちらも同じです。

interface AStruct {
  value: string;
}

interface A {
  new (value: string): AStruct;
}

function fn(Class: A) {
  return class extends Class {};
}

class AA implements AStruct {
  constructor(public value: string) {}
}

fn(AA);

// ---

type BStruct = {
  value: string;
};

type B = {
  new (value: string): BStruct;
};

function fn(Class: B) {
  return class extends Class {};
}

class BB implements BStruct {
  constructor(public value: string) {}
}

fn(BB);

重複

typeはスコープ内で重複できません。

interface A {}
interface A {}

// Duplicate identifier 'B'.
type B = {};
type B = {};

三項演算子

typeのみ使えます。

type A<T extends boolean> = T extends true ? 'foo' : 'bar';

const truthyValue: A<true> = 'foo';
const falsyValue: A<false> = 'bar';

タプル

typeは数も同じでなければならない。

interface A {
    0: number;
    1: string;
    2: number;
}

type B = [number, string, number];

// ok
const a: A = [0, '1', 2, 3];

// Type '[number, string, number, number]' is not assignable to type 'B'.
//   Types of property 'length' are incompatible.
//     Type '4' is not assignable to type '3'.
const b: B = [0, '1', 2, 3]

インデックス署名付きだけのオブジェクトへの自動変換

typeの方は{[key: string]: string}のようなインデックス署名が無いにも関わらず、それを期待する引数として渡せます。

interface A {
  value: string;
}

type B = {
  value: string;
};

function fn(object: { [key: string]: string }): void {}

const a: A = {
  value: "a",
};

const b: B = {
  value: "b",
};

// Argument of type 'A' is not assignable to parameter of type '{ [key: string]: string; }'.
// Index signature is missing in type 'A'.
fn(a);
fn(b);

これに関してはanyよりは安全なタイプガードができるかなとしばらく考えましたが、普通にifで絞り込んだほうがいいという結論になりました。。