Closed11

TypeScriptでType AliasesとInterfacesどちらを使うべきか

Shingo YamazakiShingo Yamazaki

interfaceとtypeの違い、そして何を使うべきかについて

「3. どちらを使う?」ではどちらかに決定してるわけではなく、双方の言い分をまとめてる。

interface 派

始めの部分で申し上げた通りTSに関する記事を探したらinterfaceを使うようにと言ってるところも多いです。そしてその根拠としては主に拡張性をあげています。公式ドキュメントでもinterfaceのほうが拡張にオープンなJavascriptのオブジェクト動作方式に似ているという理由でinterfaceをおすすめしています。

type 派

  • interface で定義できるのはオブジェクトとクラスだけだが、 type は他の型でも使える (string literal typesとか)
  • interface は2回定義すると拡張できる(言い換えると、知らないうちに拡張される可能性がある)
  • interface でできることはだいたい type でもできるので type でいい
Shingo YamazakiShingo Yamazaki

Google TypeScript Style Guide

when declaring types for objects, use interfaces instead of a type alias for the object literal expression.

というわけでinterface推し。

Why?
These forms are nearly equivalent, so under the principle of just choosing one out of two forms to prevent variation, we should choose one. Additionally, there also interesting technical reasons to prefer interface. That page quotes the TypeScript team lead: Honestly, my take is that it should really just be interfaces for anything that they can model. There is no benefit to type aliases when there are so many issues around display/perf.

で、ここで参照してるのが TypeScript: Prefer Interfaces — @ncjamieson という記事。
これはたしか type 使うと生成される .d.ts のサイズがでかくなる可能性がある、みたいな話だったはず。

Shingo YamazakiShingo Yamazaki

TypeScript: Documentation - Everyday Types > Differences Between Type Aliases and Interfaces

冒頭で

Type aliases and interfaces are very similar, and in many cases you can choose between them freely

と書いてるので多くのケースではどちらでもよい

Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.

type は一度定義されたものはre-open(再定義、の意味?)して新しいプロパティを追加できない一方、
interface はいつでもextendable。

  • 型をextendするときは
    • interface: extends を使う
    • type: intersection type (&)を使う
  • interface は二重定義によりフィールドを足すことが可能だが、type はエラー

がサンプルコードつきで載ってる。

You’ll learn more about these concepts in later chapters, so don’t worry if you don’t understand all of these right away.

の後ろにinterfaceとtypeの挙動の違いサマリ的な箇条書きがあり

  • TS 4.2 以前は、type aliases に変換をかませたとき(Omit とか)に、エラーメッセージでエイリアス名が表示されなかった

  • Type aliases は同じ定義が複数あるとマージしてくれずエラー(↑に書いてる話)

  • Interfaces はオブジェクトの構造を宣言するのに使えるが、プリミティブな値に名前をつける用途は不可

  • Interfaces 名はエラーメッセージで必ず表示されるが、名前を指定して使われているときのみ

結論としては

For the most part, you can choose based on personal preference, and TypeScript will tell you if it needs something to be the other kind of declaration. If you would like a heuristic, use interface until you need to use features from type.

Shingo YamazakiShingo Yamazaki

Types vs. interfaces in TypeScript - LogRocket Blog

Type Aliases と Interfaces の違いとして挙げてるのは、

  • interface は Declaration Merging が可能、type はNG
  • interface は class から extends できるが type はNG
    • class が implements できるのはどちらもOK
  • 複数の interface を & で組み合わせて交差型(結果は type)が作れるが、逆(複数の type から interfaceを作る)は不可
  • 複数の interface を | で組み合わせてUnion(結果は type)が作れるが、逆(複数の type から interfaceを作る)は不可
  • タプルは type のみ

What should I use?

Interfaces are better when you need to define a new object or method of an object. For example, in React applications, when you need to define the props that a specific component is going to receive, it’s ideal to use interface over types:

Types are better when you need to create functions, for example. Let’s imagine that we have a function that’s going to return an object called, type alias is more recommended for this approach:

type Person = {
  name: string,
  age: number
};

type ReturnPerson = (
  person: Person
) => Person;

const returnPerson: ReturnPerson = (person) => {
  return person;
};

後者の、これがtypeが適している理由がよくわからない。。。

また、最後に

You should not start to use one and delete the other. Instead of doing that, start to refactor slowly, thinking of what makes more sense to that specific situation.

こう書いていて、そうだよね結局どちらかに一意に決められるものではないよねという。


交差型についてはTypeScript 公式ドキュメント Interfaces vs. Intersections

We just looked at two ways to combine types which are similar, but are actually subtly different. With interfaces, we could use an extends clause to extend from other types, and we were able to do something similar with intersections and name the result with a type alias. The principle difference between the two is how conflicts are handled, and that difference is typically one of the main reasons why you’d pick one over the other between an interface and a type alias of an intersection type.

Shingo YamazakiShingo Yamazaki

Tidy TypeScript: Prefer type aliases over interfaces

共通点

  • class の implements で使える
  • オブジェクトリテラルの型アノテーションで使える
  • 再帰的な型構造 (recursive type structures) に使える

Declaration merging

  • TS本体では lib.d.ts 内でこれを多用してるよ
  • FormData(DOM APIに定義されてる)みたいなinterface名を使ってしまうと、知らないうちにマージされて本来ないはずのプロパティにアクセスするコード書いてもコンパイル通るみたいなこと起きる

Index access types

たとえば Record<string, string> = { [key: string]: string } などは index access types。
こういった型に対して type は渡せるが interface は渡せない。

(Playground)

これは、先ほどのdeclaration mergingという機能により、interface は宣言時点ではまだすべてのプロパティが既知ではないので、
こういった型に対しては渡せなくなる。

Shingo YamazakiShingo Yamazaki

TypeScript使いに質問です。InterfaceとTypeの使い分けはどうしていますか?どっちか一方だけに統一していますか?また有用なリンクがありますか? - Quora

判断の立脚点となるのはinterfaceが何であって、今宣言しようとしているその型は本質的にinterfaceなのかどうかです。

パーシャルクラスはなぜ必要か
ライブラリでinterfaceパーシャルクラスが有用になるケースというのは、JSとの兼ね合いで、たとえばwindow.*のグローバル変数の宣言のケースがあります。そういう形で拡張をしていかないなら、最初からTypeScriptだけでコーディングをしているならパーシャルクラスが有用なことはほとんどないでしょう。たとえばReactのPropsでアドホックに拡張が必要になるケースはほとんどないはずです。交差型でもいいはずですしね。

ということから、ライブラリであっても独自の型定義でユーザーが同名で拡張するような使われ方を想定してないのであればtypeでいいのでは感。

Shingo YamazakiShingo Yamazaki

TypeScript: Prefer Interfaces — @ncjamieson

これは以前も読んだ記事。
冒頭に

Some time after this blog post was written, Anders Hejlsberg opened a PR that preserves type aliases for union and intersection types. That PR’s changes should included in TypeScript 4.2, so when that version is released, the reasons for preferring interfaces might be less compelling.

て追記されてた。
が、記事中のIIFEの例は改善されてないように見えた(Playground)。

記事の主張

type alias使うと、生成される.d.ts内でエイリアスがインライン展開されるケースがあって、その場合interfaceと比べて.d.tsのサイズが肥大化するよという話。

const read = (() => {
  type ReadCallback = (content: string) => string;
  return function (path: string, callback: ReadCallback) {};
})();

// .d.ts
declare const read: (path: string, callback: (content: string) => string) => void;

となる。
一方interfaceはコンパイルエラーでそもそもこういう書き方はできない。

Exported variable 'read' has or is using private name 'ReadCallback'.
Shingo YamazakiShingo Yamazaki

Interface vs Type alias in TypeScript 2.7 | by Martin Hochel | Medium

記事が2018年5月。TS 2.7 の情報なので古い部分あるかも。

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

  2. you cannot use extends on an interface with type alias if you use union operator within your type definition

  3. declaration merging doesn’t work with type alias

  4. union types 使った type alias は class に implement できないよ

Playground

  1. 同様に、union types 使った type alias は interace に extends できないよ

Playground

  1. 宣言のマージは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:

  • type のほうが宣言が短く書ける
  • "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 = {...}

→これは、別に全部 interface に統一すれば一貫性あるのでは?? 🤔

Shingo YamazakiShingo Yamazaki

typescript-cheatsheets/react: Cheatsheets for experienced React developers getting started with TypeScript

https://github.com/typescript-cheatsheets/react#types-or-interfaces にtypeとinterfaceの比較あり

TL; DR; 的には

Use Interface until You Need Type - orta.

というわけでinterface使っとけ、なんだけど、その後に

consider using type for your React Component Props and State, for consistency and because it is more constrained.

とかも言ってるので主張がよくわからないことになってる

Shingo YamazakiShingo Yamazaki

Preferring Interfaces Over Intersections

公式Wiki。
interfaceを推奨する理由をこのへんでいろいろ挙げてるけど理解できてない。

Interfaces create a single flat object type that detects property conflicts, which are usually important to resolve! Intersections on the other hand just recursively merge properties, and in some cases produce never. Interfaces also display consistently better, whereas type aliases to intersections can't be displayed in part of other intersections. Type relationships between interfaces are also cached, as opposed to intersection types as a whole. A final noteworthy difference is that when checking against a target intersection type, every constituent is checked before checking against the "effective"/"flattened" type.


プロパティが衝突したとき、interfaceではエラーにしてくれるけどtype aliasはしてくれないという挙動の違いを忘れてた。

type aliasだと

type Foo = {
  x: number
}

type Bar = { 
  x: string
}

type Baz = Foo & Bar // x: never になる
const baz: Baz = {}
このスクラップは2021/05/31にクローズされました