interface
もtype
もどちらも新しい型を定義する為に使います。以下は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;
}
type B = { /* 同じ */ }
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 true ? 'foo' : 'bar';
const truthyValue: A = 'foo';
const falsyValue: A = '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
で絞り込んだほうがいいという結論になりました。。