💡

【TypeScript / satisfies】型注釈(type annotation) はもういらない?

2023/05/21に公開

■はじめに

TypeScript v4.9から利用可能になったsatisfiesOperator
https://www.typescriptlang.org/docs/handbook/release-notes/typescript-4-9.html

利用していくうちに
satisfiesを使えば型注釈 (type annotation)は必要ないのでは?

という考えが浮かび記事にしてみました。

型注釈 (type annotation) 参考資料

■結論

ほとんどのケースで、
型注釈 (type annotation)ではなく、satisfiesを利用した方がよいと考えています。

satisfiesの機能・動作の説明とともに後述していきます。

■satisfies とは

ざっくり
型推論をいい感じに効かせながら型を限定できる機能

と理解しました。

■satisfies の良いところを動作とともに確認していく

satisfies の良いところは下記2つを実現できるところにあります。

  1. 型を限定できる
  2. 型推論をいい感じに効かせることができる

最初下記のように定義したとします。

type Color = {
  // Mapped Typesで key を限定
  [key in 'red' | 'blue' | 'green']: string | number[]
}
const colorsSatisfies= {
  red: '#ff0000',
  blue: '#0000ff',
  green: [0, 255, 0],
} satisfies Color;

1. 型を限定できる

型チェックが効いてくれる

const colorsSatisfies= {
  red: '#ff0000',
  blue: '#0000ff',
  green: [0, 255, 0],
  // NG: 許容しない型を定義しようとするとエラー
  yellow: [255, 255, 0],
} satisfies Color;

2. 型推論を効かせることができる

型推論が効くので、下記型になります。

  • red: string
  • blue: string
  • green: number[]
// NG: string は map を持っていないためエラー
colorsSatisfies.red.map((v) => v)

// OK
colorsSatisfies.green.map((v) => v)

■satisfies を利用しない場合

型を限定したい場合ほとんどの場合型注釈 (type annotation)を利用すると思います。

type Color = {
  // Mapped Typesで key を限定
  [key in 'red' | 'blue' | 'green']: string | number[]
}
const colorsAnnotation: Color = {
  red: '#ff0000',
  blue: '#0000ff',
  green: [0, 255, 0],
}

◯型注釈でも型は限定できる

型チェックが効いてくれる

const colorsAnnotation: Color = {
  red: '#ff0000',
  blue: '#0000ff',
  green: [0, 255, 0],
  // NG: yellow を許容しないためエラー
  yellow: [255, 255, 0],
}

◯型注釈ではいい感じに型推論を効かせることができない

型注釈 (type annotation)の場合、型推論がいい感じに効いてくれません。
下記の型になる。

  • red: string | number[]
  • blue: string | number[]
  • green: string | number[]
// NG: string のため当然エラー
colorsAnnotation.red.map((v) => v)
// NG: string の可能性もあるためエラー
colorsAnnotation.green.map((v) => v)

このパターンでmap関数 を利用したい場合、型ガードを利用すればできます。
(なかなか、しんどい実装...)

if (typeof colorsAnnotation.green !== 'string') {
  // OK
  colorsAnnotation.green.map((v) => v)
}

■型注釈はもう必要ないのか?

satisfies型を限定できるかつ型推論をいい感じに効かせることができるので、
型注釈 (type annotation)もういらないのでは? と思えてきます。

ただ、
型が推論できない場面ではsatisfiesを利用することはできないです。

type UnionColor = 'red' | 'blue' | 'green'

// NG
let colors satisfies UnionColor
// OK
let colors: UnionColor

つまり、推論すべき値が存在しない場面では
satisfies を利用できず、
型注釈を利用するしかないです。

型が推論できない場面 を除いてはsatisfiesを使った方がいいと思います。

最後に さらに便利なsatisfiesの使い方を紹介していきます。

as constsatisfiesを一緒に使うと便利

satisfies と as const を組み合わせて使うと、3つの恩恵を享受できます。

  1. satisfiesにより型を限定することができる
  2. as constにより値が widening されない
  3. as constにより readonly の値にできる

TypeScript の Widening
※ widening の例: '#ff0000'型 -> string型に広がって推論されてしまう

1. satisfiesにより 型を限定することができる

type Color = {
  // Mapped Typesで key を限定
  [key in 'red' | 'blue' | 'green']: string | readonly number[]
}
const colorsAsConstSatisfies = {
  red: '#ff0000',
  blue: '#0000ff',
  green: [0, 255, 0],
  // NG: yellow を許容しないためエラー 型を限定することができる
  yellow: [255, 255, 0],
} as const satisfies Color;

as const だけだと型を限定できない

const colors = {
  red: '#ff0000',
  blue: '#0000ff',
  green: [0, 255, 0],
  // キーを限定できない yellow を許容する
  yellow: [255, 255, 0],
} as const;

2. as constにより値が widening されない

type Color = {
  // Mapped Typesで key を限定
  [key in 'red' | 'blue' | 'green']: string | readonly number[]
}
const colorsAsConstSatisfies = {
  red: '#ff0000',
  blue: '#0000ff',
  green: [0, 255, 0],
} as const satisfies Color;

型推論の結果

// widening されない
const colorsAsConstSatisfies = {
  readonly red: '#ff0000';
  readonly blue: '#0000ff';
  readonly green: readonly [0, 255, 0];
}

satisfiesだけだと widening されてしまう

const colorsSatisfies = {
  red: '#ff0000',
  blue: '#0000ff',
  green: [0, 255, 0],
} satisfies Color;

型推論の結果

// widening されてしまう
const colorsSatisfies = {
  red: string;
  blue: string;
  green: number[];
}

3. as constにより readonly の値にできる

type Color = {
  // Mapped Typesで key を限定
  [key in 'red' | 'blue' | 'green']: string | readonly number[]
}
const colorsAsConstSatisfies = {
  red: '#ff0000',
  blue: '#0000ff',
  green: [0, 255, 0],
} as const satisfies Color;

red に代入しようとすると

// NG: readonly のためエラー 
colorsAsConstSatisfies.red = 'red'

satisfiesだけだと readonly の値にならない

redに代入しようとすると

const colorsSatisfies = {
  red: '#ff0000',
  blue: '#0000ff',
  green: [0, 255, 0],
} satisfies Color;

// OK: readonly ではないため
colorsSatisfies.red = 'red'

■最後に

読んでいただきありがとうございます!
ご指摘、意見などあれば気軽にコメントお待ちしております!

参考資料

ありがとうございました!

Discussion