Closed66

【サバイバルTypeScript】『読んで学ぶTypeScript』セクションの読書メモ

Daisuke KusatakeDaisuke Kusatake

オブジェクトの分割代入

オブジェクトの分割代入 (destructuring assignment) | TypeScript入門『サバイバルTypeScript』

分割代入 - JavaScript | MDN

const lunch = { rice: undefined, main: "カラアゲ", soup: "わかめスープ" }
const { soup } = lunch
console.log(soup)
// "わかめスープ"
Daisuke KusatakeDaisuke Kusatake

分割代入のデフォルト値

該当する値がundefinedのときにデフォルト値が代入される。

const { rice = "中", main: "ハンバーグ", soup: "コーンスープ" } = lunch
console.log(rice, main, soup)
// "中", "カラアゲ", "わかめスープ"
Daisuke KusatakeDaisuke Kusatake

オブジェクトをループする方法 | TypeScript入門『サバイバルTypeScript』

.call()の利用目的?

for-in文はプロトタイプのプロパティも含めてループする仕様があります。そのため、プロトタイプが変更されると、意図しないところでfor-inのループ回数が変わることがあります。
したがって、for-inで反復処理を書く場合は、hasOwnPropertyでプロパティがプロトタイプのものでないことをチェックしたほうが安全です。

const foo = { a: 1, b: 2, c: 3 };
Object.prototype.hi = "Hi!";
for (const prop in foo) {
  if (Object.prototype.hasOwnProperty.call(foo, prop)) {
    console.log(prop, foo[prop]);
    // a 1
    // b 2
    // c 3  の順で出力される
  }
}

Function.prototype.call() - JavaScript | MDN

なるほど

Daisuke KusatakeDaisuke Kusatake
Daisuke KusatakeDaisuke Kusatake

Bingちゃんに聞いてみた。

リスコフの置換原則について調べてみたよ。😊

リスコフの置換原則とは、オブジェクト指向プログラミングにおける原則の一つで、基底型(親クラス)と派生型(子クラス)の関係について定義したものだって。[1][2][3]

この原則では、基底型の変数に派生型の値を代入しても、プログラムの振る舞いが変わらないことが求められるんだって。[1:1][2:1][3:1]

例えば、CarクラスとTaxiクラスがあって、TaxiクラスはCarクラスを継承しているとするね。[3:2]

Carクラスにはstartメソッドがあって、エンジンをかける処理をするとするね。[3:3]

Taxiクラスはstartメソッドをオーバーライドして、エンジンをかけるだけでなく、メーターも起動する処理をするとするね。[3:4]

この場合、Car型の変数にTaxi型の値を代入しても、startメソッドを呼び出すときにメーターが起動することは期待されないから、プログラムの振る舞いが変わってしまうんだって。[3:5]

だから、リスコフの置換原則に違反しているんだって。[3:6]

リスコフの置換原則に従うためには、派生型は基底型の仕様や契約を守らなければならないんだって。[2:2][3:7]

具体的には、

  • 派生型は基底型よりも弱い事前条件(入力や引数)で済むようにしなければならない
  • 派生型は基底型よりも強い事後条件(出力や戻り値)を満たさなければならない
  • 派生型は基底型よりも弱い不変条件(常に成り立つべき性質)で済むようにしなければならない
    というルールがあるんだって。[2:3][3:8]

リスコフの置換原則を守ることで、

  • 基底型と派生型の互換性や拡張性が高まる
  • サブタイピングやポリモーフィズムが正しく機能する
  • コードの再利用性や保守性が向上する
    というメリットがあるんだって。[2:4][3:9]

リスコフの置換原則はSOLID原則(オブジェクト指向設計で重要視される5つの原則)のLに相当するんだって。[2:5]

脚注
  1. https://ja.wikipedia.org/wiki/リスコフの置換原則 ↩︎ ↩︎

  2. https://qiita.com/yuki153/items/142d0d7a556cab787fad ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. https://zenn.dev/chida/articles/5373e135be11f0 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

Daisuke KusatakeDaisuke Kusatake

配列の型注釈 (type annotation) | TypeScript入門『サバイバルTypeScript』

Type[]とArray<T>どちらを使うべきか?

Type[]

let array: number[];
array = [1, 2, 3];

Array<T>

let array: Array<number>;
array = [1, 2, 3];

メンタルモデル的にはどちらがいいんだろう?

配列であること を強調したいなら Array<T> が有利で、 配列要素が数値であること を強調したいなら Type[] が有利っていう関係な気がする

Daisuke KusatakeDaisuke Kusatake

配列要素へのアクセス | TypeScript入門『サバイバルTypeScript』

超重要ノウハウだ

JavaScriptでは存在しないインデックスで要素アクセスした場合、エラーにならず、代わりにundefinedが得られると説明しましたが、TypeScriptでも不在要素へのアクセスについて、コンパイラーが警告することはありません。

要素アクセスで得た値はstringとundefinedどちらの可能性もありながら、TypeScriptは常にstring型であると考えるようになっています。そのため、要素アクセスでundefinedが返ってくる場合のエラーはTypeScriptでは発見できず、JavaScript実行時に判明することになります。

TypeScriptにこの問題を指摘してもらうようにするには、コンパイラーオプションのnoUncheckedIndexedAccessを有効にします。

noUncheckedIndexedAccess | TypeScript入門『サバイバルTypeScript』

Daisuke KusatakeDaisuke Kusatake

配列の分割代入 (destructuring assignment) | TypeScript入門『サバイバルTypeScript』

残余部分の代入

JavaScriptの配列を分割代入するときに、残余パターン(...)を用いて、配列の残りの部分を取り出して変数に代入できます。

const oneToFive = [1, 2, 3, 4, 5];
const [one, ...rest] = oneToFive;
console.log(one);
// 1
console.log(rest);
// [ 2, 3, 4, 5 ]

このときTypeScriptでは、残余部分の型は配列のnumber[]になります。

Daisuke KusatakeDaisuke Kusatake

これ、残余部分が空配列になるパターンあると思うんだけど
配列要素の型が保持されてていいのかな?

const onlyOne = [1]
const [one, ...empty] = onlyOne
console.log(one)
// 1
console.log(empty)
// []
Daisuke KusatakeDaisuke Kusatake

配列の共変性 (covariance) | TypeScript入門『サバイバルTypeScript』

要注意事項だ

配列は共変が許される

型の世界の話で、共変とはその型自身、もしくは、その部分型(subtype)が代入できることを言います。たとえば、Animal型とDog型の2つの型があるとします。DogはAnimalの部分型とします。共変であれば、Animal型の変数にはAnimal自身とその部分型のDogが代入できます。

つまり、型のスーパーセットにはサブセットを代入できると。

一見するとこの性質は問題なさそうです。ところが、次の例のようにanimals[0]をAnimal型の値に置き換えると問題が起こります。

interface Animal {
  isAnimal: boolean;
}
interface Dog extends Animal {
  wanwan(): string; // メソッド
}
 
const pochi = {
  isAnimal: true,
  wanwan() {
    return "wanwan"; // メソッドの実装
  },
};
 
const dogs: Dog[] = [pochi];
const animals: Animal[] = dogs;
animals[0] = { isAnimal: true }; // 同時にdogs[0]も書き換わる
const mayBePochi: Dog = dogs[0];
mayBePochi.wanwan();
// JS実行時エラー: mayBePochi.wanwan is not a function

例示してあるのが
シャローコピー元のオブジェクトがコピー先によって変更されたケースだと思うんだけど
そういうケースにだけとりあえず注意してればいいのかしら……

Daisuke KusatakeDaisuke Kusatake

タプル (tuple) | TypeScript入門『サバイバルTypeScript』

タプルを使う場面

TypeScriptで非同期プログラミングをする時に、時間のかかる処理を直列ではなく並列で行いたい時があります。そのときTypeScriptではPromise.all()というものを使用します。このときタプルが役に立ちます。

const tuple: [string, number] = await Promise.all([
  takes3Seconds(),
  takes5Seconds(),
]);

この構文、多用しそう~

Daisuke KusatakeDaisuke Kusatake

列挙型 (enum) | TypeScript入門『サバイバルTypeScript』

JavaScriptにコンパイルすると次のようなコードになります。

var Position;
(function (Position) {
    Position[Position["Top"] = 0] = "Top";
    Position[Position["Right"] = 1] = "Right";
    Position[Position["Bottom"] = 2] = "Bottom";
    Position[Position["Left"] = 3] = "Left";
})(Position || (Position = {}));

パッと見たときの入れ子構造にぎょっとしたけど、ゆっくり読み直したらなんとなく理解できるかも。

Position[Position["Top"] = 0] = "Top";

Position["Top"] = 0は返り値が0だから、全体を通してみるとPosition[0] = "Top"ということ。

つまり、この2つをを一行で定義している。

  • Position["Top"] = 0
  • Position[0] = "Top"

キーバリューが相互に指定しあえる関係にあるのがEnumなのかもしれない。

Positionオブジェクトの中身を見たらやはりそういうことな気がする。

// Position
{
    "0": "Top",
    "1": "Right",
    "2": "Bottom",
    "3": "Left",
    "Top": 0,
    "Right": 1,
    "Bottom": 2,
    "Left": 3
}
Daisuke KusatakeDaisuke Kusatake

列挙型(enum)の問題点と代替手段 | TypeScript入門『サバイバルTypeScript』

数値列挙型には型安全上の問題がある

数値列挙型は、number型なら何でも代入できるという型安全上の問題点があります。次の例は、値が0と1のメンバーだけからなる列挙型ですが、実際にはそれ以外の数値を代入できてしまいます。

列挙型には、列挙型オブジェクトに値でアクセスすると、メンバー名を得られる仕様があります。これにも問題があります。メンバーに無い値でアクセスしたら、コンパイルエラーになってほしいところですが、そうなりません。

コンパイルエラーが出ない型を使うのはTypeScript採用している利点が薄まる気がするから使わない方がよさそう~

Daisuke KusatakeDaisuke Kusatake

ユニオン型 (union type) | TypeScript入門『サバイバルTypeScript』

ユニオン型と絞り込み

ユニオン型string | nullがstringなのかnullなのかを判定したいときは、TypeScriptの絞り込み(narrowing)を用います。絞り込みをするにはいくつかの方法がありますが、代表例はif文です。条件分岐で変数が文字列型かどうかをチェックすると、同じ変数でも分岐内ではstring | null型がstring型だと判定されます。

const maybeUserId: string | null = localStorage.getItem("userId");
 
const userId: string = maybeUserId; // nullかもしれないので、代入できない。
 
if (typeof maybeUserId === "string") {
  const userId: string = maybeUserId; // この分岐内では文字列型に絞り込まれるため、代入できる。
}

分岐処理で対象の型が通過しないことを確約できればコンパイルエラーにならないんだね~

Daisuke KusatakeDaisuke Kusatake

判別可能なユニオン型 (discriminated union) | TypeScript入門『サバイバルTypeScript』

type UploadStatus = InProgress | Success | Failure;
type InProgress = { done: boolean; progress: number };
type Success = { done: boolean };
type Failure = { done: boolean; error: Error };

たとえば、上のUploadStatusを判別可能なユニオン型に書き直すと、次のようになります。

type UploadStatus = InProgress | Success | Failure;
type InProgress = { type: "InProgress"; progress: number };
type Success = { type: "Success" };
type Failure = { type: "Failure"; error: Error };

ディスクリミネーター もユニオン型を絞り込む方法のひとつってことなんだね~

ディスクリミネータに使える型

ディスクリミネータに使える型は、リテラル型とnull、undefinedです。

  • リテラル型
    • 文字列リテラル型: (例)"success"、"OK"など
    • 数値リテラル型: (例)1、200など
    • 論理値リテラル型: trueまたはfalse
  • null
  • undefined
Daisuke KusatakeDaisuke Kusatake

判別可能なユニオン型 (discriminated union) | TypeScript入門『サバイバルTypeScript』

ディスクリミネータを変数に代入する場合

type Shape =
  | { type: "circle"; color: string; radius: number }
  | { type: "square"; color: string; size: number };
 
function toCSS(shape: Shape) {
  const { type, color } = shape;
  //      ^^^^ディスクリミネータ
  switch (type) {
    case "circle":
      return {
        background: color,
        borderRadius: shape.radius,
      };
 
    case "square":
      return {
        background: color,
        width: shape.size,
        height: shape.size,
      };
  }
}

オブジェクトのプロパティを変数に格納してそれをswitch文で分岐させるのめちゃめちゃ使いそう

Daisuke KusatakeDaisuke Kusatake

型アサーション「as」(type assertion) | TypeScript入門『サバイバルTypeScript』

型アサーション「as」(type assertion)

型推論(コンパイル時の型チェック)を上書きする。

const value: string | number = "this is a string";
const strLength: number = (value as string).length;
const value: string | number = "this is a string";
const strLength: number = (<string>value).length;
Daisuke KusatakeDaisuke Kusatake

TypeScriptわからん状態でコードを読んでた時に読み飛ばしてた謎の構文のうちの一つが解けた

あとはinterfaceかな

Daisuke KusatakeDaisuke Kusatake

constアサーション「as const」 (const assertion) | TypeScript入門『サバイバルTypeScript』

constアサーション「as const」 (const assertion)

readonlyとconst assertionの違い

どちらもオブジェクトのプロパティをreadonlyにする機能は同じですが、以下が異なります。

readonlyはプロパティごとにつけられる

const assertionは再帰的にreadonlyにできる

ホントに定数としてオブジェクトを使いたいときに便利だ

Daisuke KusatakeDaisuke Kusatake

definite assignment assertion | TypeScript入門『サバイバルTypeScript』

definite assignment assertion

変数宣言の変数名やプロパティ名のあとに!を書きます。

自分では使わないようにするだろうけど、
ひとのコード見たときになんだっけこれ?ってなりそう

「変数の後ろにビックリマーク」はググラビリティ低いし。

Daisuke KusatakeDaisuke Kusatake

あわせて

  • 非Nullアサーション
  • non-null assertion
let num: number;
initNum();
console.log(num! * 2); // エラーにならない
//             ^非Nullアサーション
function initNum() {
  num = 2;
}
Daisuke KusatakeDaisuke Kusatake

never型 | TypeScript入門『サバイバルTypeScript』

neverは何も入りえない型である特性を利用する。
コンパイル時にnever型の変数に値が入ると判断されるということは、本来では通らないはずの分岐に入ってしまっているということ。
網羅性チェック

例外クラスで網羅性チェックをするとよい。

class ExhaustiveError extends Error {
  constructor(value: never, message = `Unsupported type: ${value}`) {
    super(message);
  }
}
function printLang(ext: Extension): void {
  switch (ext) {
    case "js":
      console.log("JavaScript");
      break;
    case "ts":
      console.log("TypeScript");
      break;
    default:
      throw new ExhaustiveError(ext);
  }
}
Daisuke KusatakeDaisuke Kusatake

制御フロー分析と型ガードによる型の絞り込み | TypeScript入門『サバイバルTypeScript』

ユーザー定義の型ガード関数

function isWizard(player: Player): player is Wizard {
  return "castMagic" in player;
}

function attack(player: Wizard | SwordMan) {
  if (isWizard(player)) {
    player.castMagic();
  } else {
    player.slashSword();
  }
}

is is 何?

Daisuke KusatakeDaisuke Kusatake

unknown型 | TypeScript入門『サバイバルTypeScript』

function isEmail(value: unknown): value is Email {
  if (typeof value !== "object" || value === null) {
    return false;
  }
  // 型アサーションでvalueをEmail型に近づける
  const email = value as Record<keyof Email, unknown>;
  // 各プロパティのチェック
  if (typeof email.from !== "string") {
    return false;
  }
  return true;
}

ここはつまりどういうこと?

  const email = value as Record<keyof Email, unknown>;
Daisuke KusatakeDaisuke Kusatake

元の疑問点に立ち返ると

Record<keyof Email, unknown>

keyof EmailでEmailのキーと同値のプリミティブ型を列挙した型定義を提供する。

Daisuke KusatakeDaisuke Kusatake
value as Record<keyof Email, unknown>

型アサーション as でEmail型と同じキーを持ち、値がunknown型であるプロパティとなる型としてvalueの型を定義する。

Daisuke KusatakeDaisuke Kusatake

valueはEmail型と同じキーを持つことをコンパイラに教えたので、以降の型チェックでプロパティを持たないとするコンパイラエラーにならない。

Daisuke KusatakeDaisuke Kusatake

関数宣言 (function declaration) | TypeScript入門『サバイバルTypeScript』

関数

関数宣言

引数と返り値に型注釈を書ける。

function levelUp(lv: number): number {
  return num + 2;
}

引数の型注釈の省略はany型となり、noImplicitAnyでコンパイルエラーとすることもできる。

返り値の型注釈の省略は型推論される。
返り値が一位に定まらない場合は返り値はユニオン型になる。

Daisuke KusatakeDaisuke Kusatake

関数宣言 (function declaration) | TypeScript入門『サバイバルTypeScript』

関数式 (function expression) | TypeScript入門『サバイバルTypeScript』

こうやって体系的に見ていくと、 関数宣言関数式 の使い分けとか理解がおざなりだったなぁ……

関数宣言

function hello(name: string): string {
  return `こんにちは!${name}です!`
}

hello("シロ")
// こんにちは!シロです!

関数式

// 関数式は関数名を省略できるため
// ここでは関数名を省略して無名関数を代入している
const hello = function (name: string): string {
  return `こんにちは!${name}です!`
}
hello("シロ")
// こんにちは!シロです!
Daisuke KusatakeDaisuke Kusatake

関数式と型注釈

関数型の変数に関数式を代入する場合は、引数の型注釈を省略しても、型推論が効きます。変数の型情報から、引数の型がわかるからです。

確かに

Daisuke KusatakeDaisuke Kusatake

実務では省略することの方が多いんだろうか?

関数内の処理を読むときには型を書いてくれていた方が読みやすそうではある。

あと、勘だけど
コンパイルエラーが関数式側で起きるのと呼び出し側で起きるのとで違いがありそう。

Daisuke KusatakeDaisuke Kusatake

アロー関数 (arrow function) | TypeScript入門『サバイバルTypeScript』

const increment = (num: number): number => num + 1;
//                             ^^^^^^^^戻り値の型注釈

引数のカッコを省略した場合は、引数と戻り値のどちらも型注釈を書けません。

const increment = num => num + 1;

ワンライナーで書こうと思ったときに1行の情報量増えちゃうな……

避けたいかも

Daisuke KusatakeDaisuke Kusatake

また、JavaScriptにはスクリプトモードとモジュールモードがあります。モジュールモードのJavaScriptでは、exportやimportの構文が使えます。このモードでは自動的にstrictモードになります。そのため、モジュールモードでshowThisを実行すると、thisの値はundefinedになります。

なるほどね

Daisuke KusatakeDaisuke Kusatake

従来の関数はコンストラクタとして呼び出せることを説明しましたが、コンストラクタとして呼び出した場合、thisは生成中のオブジェクトを指します。

なるほどね

Vue.jsでthisvm.$data()のプロパティだったりを呼べるのはこの性質ってことか

Daisuke KusatakeDaisuke Kusatake

従来の関数とアロー関数の違い | TypeScript入門『サバイバルTypeScript』

まとめ助かります

従来の関数のthisが指すもの

文脈 thisの値
通常の呼び出し
showThis()
グローバルオブジェクト(Window)
通常の呼び出し + strictモード
showThis()
undefined
メソッド呼び出し
obj.showThis()
メソッドが属するオブジェクト(obj)
コンストラクタ呼び出し
new showThis()
生成中のオブジェクト
Daisuke KusatakeDaisuke Kusatake

this引数 (this parameter) | TypeScript入門『サバイバルTypeScript』

理解が追い付いていない……

class Male {
  private name: string;
 
  public constructor(name: string) {
    this.name = name;
  }
 
  public toString(): string {
    return `Monsieur ${this.name}`;
  }
}
 
class Female {
  private name: string;
 
  public constructor(name: string) {
    this.name = name;
  }
 
  public toString(this: Female): string {
    return `Madame ${this.name}`;
  }
}

各インスタンスのtoString()を変数に代入すると意味が変わります。

const maleToStr: () => string = male.toString;
const femaleToStr: (this: Female) => string = female.toString;
 
maleToStr();
femaleToStr();

femaleToStr()のコンテキストがFemaleではないとの指摘を受けています。このコードを実行することはできません。

これはmale.toString()の戻り値をmaleToStrに代入している操作ではなくて、maleToStr()関数オブジェクトにmale.toString()関数自体をを代入しようとしている操作という理解でいいのだろうか……

Daisuke KusatakeDaisuke Kusatake

分割代入引数 (destructuring assignment parameter) | TypeScript入門『サバイバルTypeScript』

TypeScriptでデフォルト引数を型注釈する場合、オブジェクトではプロパティを?でオプションにします。

function foo({ a = 0 }: { a?: number | string }) {}

一生使うことなさそうみと使いたくなさみを感じる

デフォルト引数を与えるならば型推論が必然的に効くけど、ユニオン型でなければならない場合に使うのだろうか……

Daisuke KusatakeDaisuke Kusatake

あ、前言撤回
型エイリアスの宣言で使うのか

type Obj = { a?: number; b?: number };
function foo({ a = 0, b = 0 }: Obj = {}) {
  console.log(a + b);
}
foo();
foo({});
foo({ a: 1 });
foo({ a: 1, b: 2 });
Daisuke KusatakeDaisuke Kusatake

わからない……

デフォルト引数の型注釈と
引数の型注釈はどういう場合に結果が異なる?

Daisuke KusatakeDaisuke Kusatake

キーワード引数とOptions Objectパターン | TypeScript入門『サバイバルTypeScript』

学び~

Vue.jsのpropsをオブジェクトのプロパティで一括して渡すのもこれな気がする

Options Objectパターン

JavaScriptやTypeScriptにはキーワード引数のような言語仕様はありませんが、Options Objectパターンというデザインパターンで似たようなことができます。Options Objectパターンは複数の位置引数を受け取る代わりに、ひとつのオブジェクトを引数に受け取るように設計された関数を言います。

function func({ x, y, z }) {
  console.log(x, y, z);
}
Daisuke KusatakeDaisuke Kusatake

アサーション関数 (assertion functions) | TypeScript入門『サバイバルTypeScript』

function isDuck(animal: Animal): asserts animal is Duck {
  if (walksLikeDuck(animal)) {
    if (quacksLikeDuck(animal)) {
      return;
    }
  }
 
  throw new Error("YOU ARE A FROG!!!");
}
 
// ここではquacks()は存在しない
animal.quacks();
 
isDuck(animal);
 
animal.quacks();

is は戻り値が真偽値の場合に使えるって説明されてたけど、
asserts は例外を投げる場合にのみ使えるんだろうか?

Daisuke KusatakeDaisuke Kusatake

静的フィールド

JavaやPHPには静的フィールド(static field)という概念があります。通常のフィールドはインスタンスのプロパティですが、静的フィールドはクラスのプロパティです。JavaやPHPではstaticキーワードで静的フィールドを宣言できます。

インスタンスのフィールド

class Hoge = {}
const hoge = new Hoge
hoge.value = "hoge"
hoge.value
// "hoge"

クラスのフィールド

class Hoge = {
  static value = "hoge"
}
Hoge.value
// "hoge"
Daisuke KusatakeDaisuke Kusatake

メソッド戻り値のthis型とメソッドチェーン | TypeScript入門『サバイバルTypeScript』

このようなとき、戻り値にthisを設定することができます。上記クラスの戻り値のOperator, NewOperatorをすべてthisに置き換えると問題が解消されます。

戻り値の 型に っていうことですね。

class Operator {
  protected value: number;
 
  public constructor(value: number) {
    this.value = value;
  }
 
  public sum(value: number): this {
    this.value += value;
    return this;
  }
 
  public subtract(value: number): this {
    this.value -= value;
    return this;
  }
 
  public multiply(value: number): this {
    this.value *= value;
    return this;
  }
 
  public divide(value: number): this {
    this.value /= value;
    return this;
  }
}
 
class NewOperator extends Operator {
  public constructor(value: number) {
    super(value);
  }
 
  public power(value: number): this {
    this.value **= value;
    return this;
  }
}
 
const op: NewOperator = new NewOperator(2);
op.power(3).multiply(2).power(3); // 4096
Daisuke KusatakeDaisuke Kusatake

公称型クラス | TypeScript入門『サバイバルTypeScript』

公称型クラス

TypeScriptでは、クラスに1つでも非パブリックなプロパティがあると、そのクラスだけ構造的部分型ではなく公称型(nominal typing)になります。

たとえば、UserIdクラスとGroupIdクラスで同名になってしまっているidプロパティをプライベートにするだけで、相互の代入が不可能になります。

Daisuke KusatakeDaisuke Kusatake

interfaceとtypeの違い | TypeScript入門『サバイバルTypeScript』

interfaceとtypeの違い

内容 インターフェース 型エイリアス
継承 可能 不可。ただし交差型で表現は可能
継承による上書き 上書きまたはエラー フィールド毎に交差型が計算される
同名のものを宣言 定義がマージされる エラー
Mapped Types 使用不可 使用可能

継承できるかどうかが大きな違いそう。

インターフェースをもとに型エイリアスを作ってるのは見たことある気がする。

Daisuke KusatakeDaisuke Kusatake

Google TypeScript Style Guide

TypeScript supports type aliases for naming a type expression. This can be used to name primitives, unions, tuples, and any other types.

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

interface User {
  firstName: string;
  lastName: string;
}
type User = {
  firstName: string,
  lastName: string,
}

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 are 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.

このスクラップは2023/02/19にクローズされました