TypeScriptのあれこれ⑤satisfiesって?

はじめに

皆さんこんにちは。新卒2年目エンジニアの細川です。今回はTypeScriptのsatisfiesについてまとめたいと思います。satisfies単体での使い道や、as const と組み合わせた使い方についてもまとめているので、参考になれば幸いです。

 

satisfiesとは

satisfiesはTypeScript 4.9にて追加されました。

公式のドキュメントによると、「式の結果の型を変更せずに、式の型が何らかの型と一致するかどうかを検証できます。」とのことです。言葉だけではわかりづらいので、実際のコードを見てみましょう。

 

使い方

まず、以下のようなコードを考えてみます。

const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    bleu: [0, 0, 255]
//  ^^^^ タイポ! blueの打ち間違い
};

// redでは配列のメソッドを使いたい
const redComponent = palette.red.join();
// greenではstringのメソッドを使いたい
const greenNormalized = palette.green.toUpperCase();

上記の場合、bleuというタイポを防ぐための手段として、型注釈をするという方法があります。

以下のように、Record型で型注釈を行います。Record型ってなに?って方はこちらの記事を確認してみてください。

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];

// paletteはred, green, blueという名前のstring | RGB型のプロパティを持つオブジェクト型
const palette: Record<Colors, string | RGB> = {
    red: [255, 0, 0],
    green: "#00ff00",
    bleu: [0, 0, 255]
//  ~~~~ タイポしてるので、エラーが出る
};
// paletteのredはstringの可能性もあるため、配列のメソッドが使えない
const redComponent = palette.red.join();

型注釈をしてしまうと、型が変わってしまい、上記のように不都合が出る場合があります。

この場合、配列のメソッドを使うためには、型を絞り込んだりする手間が発生します。

このような場合、satisfiesを使うと型を変更することなく、タイポを防ぐことができます。

type Colors = "red" | "green" | "blue";
type RGB = [red: number, green: number, blue: number];
const palette = {
    red: [255, 0, 0],
    green: "#00ff00",
    bleu: [0, 0, 255]
//  ~~~~ タイポしてるので、エラーが出る
} satisfies Record<Colors, string | RGB>;
// 型は変更されないので、それぞれのプロパティの型に対応したメソッドを利用できる
const redComponent = palette.red.join(); // 配列型
const greenNormalized = palette.green.toUpperCase(); // string型

余剰プロパティのチェック

satisfiesを使うと、余剰プロパティチェックを行う際にも利用することができます。

余剰プロパティチェックというのはオブジェクトリテラルを直接代入する際に、余剰プロパティがチェックされるものです。気になる方はこちらを参考にしてみてください。

type Colors = "red" | "green" | "blue";

const favoriteColors: Record<Colors, unknown> = {
    "red": "yes",
    "green": false,
    "blue": "kinda",
    "platypus": false
//  ~~~~~~~~~~ platypusはColorsに含まれていないので、余剰プロパティとなり、エラーが出る。
};
// favoriteColorsの各プロパティはunknown型になってしまうので、boolean型に代入できない
const g: boolean = favoriteColors.green;

このように、余剰プロパティをチェックする際にも型が変更されてほしくない場合もあるかと思います。

ちなみに余談ですが、余剰プロパティのチェックは、リテラルの直接代入の時しかされないので、その点には注意が必要です。

const favoColosrs = {
    "red": "yes",
    "green": false,
    "blue": "kinda",
    "platypus": false
};

// 変数での代入の場合は、余剰プロパティのチェックはされない
const favoriteColors: Record<Colors, unknown> = favoColosrs

satisfiesを使った場合の余剰プロパティチェックの仕方は以下のようになります。

type Colors = "red" | "green" | "blue";

const favoriteColors = {
    "red": "yes",
    "green": false,
    "blue": "kinda",
    "platypus": false
//  ~~~~~~~~~~ platypusは余剰プロパティと判断されてエラー
} satisfies Record<Colors, unknown>;
// 型の情報がそのまま保持される
const g: boolean = favoriteColors.green;

プロパティ名のタイポや余剰プロパティのチェックはもちろん、Record型に型情報を正しく設定することで、各プロパティの型が正しいかどうかも確かめられるので、便利そうですね。

不要な変数宣言の回避

satisfiesの他の使い道として不要な変数宣言を回避するのにも使えるかなと思います。

試しに以下のようなコードを書いてみます。

type User = {
  name: string
  age: number
}

type createUser = (name: string, age: number) => User;
const createUser: createUser = (name, age) => {
  return {
    name,
    age,
    hoge: "hoge"// 余剰プロパティをエラーにしたい
  }
};

このcreateUserで余分なプロパティが無いかなどを確認するには以下のようにコードを書き換える必要があります。

const createUser: createUser = (name, age) => {
  const user: User = {
    name,
    age,
    hoge: "hoge" // 余剰プロパティチェックがなされ、エラーとなる
  }
  return user
};

このように書き換えると、型を正しくチェックすることができます。

しかし、型のチェックのために処理を変えるというのは、あまり好ましくない場合もあるかもしれません。

皆さん、ご存知の通り、TypeScriptはJavaScriptにコンパイルされます。

一つ目と二つ目のcreateUserのコンパイル結果を比べてみると以下のようになります。

 

// 1つめ
const createUser = (name, age) => {
    return {
        name,
        age,
        hoge: "hoge" // 余剰プロパティをエラーにしたい
    };
};

// 2つめ
const createUser = (name, age) => {
    const user = {
        name,
        age,
        hoge: "hoge" // 余剰プロパティチェックがなされ、エラーとなる
    };
    return user;
};

型チェックのために無駄な変数の宣言が発生してしまっています。

このような場合、satisfiesを使うことで、無駄に変数宣言をせずに、型のチェックを行うことができます。

const createUser: createUser = (name, age) => {
  return {
    name,
    age,
    hoge: "hoge"// 余剰プロパティチェックがなされ、エラーとなる
  } satisfies User
};

このようにすると、余剰プロパティのチェックを行うことができます。

また、このコードのJavaScriptへのコンパイル結果を見てみると、以下のようになります。

const createUser = (name, age) => {
    return {
        name,
        age,
        hoge: "hoge" // 余剰プロパティチェックがなされ、エラーとなる
    };
};

このように、コンパイル後のコードを変えることなく、型のチェックを行えるようになります。

JavaScriptチックな書き方をしたい場合も使えそうですね!

 

satisfies + as const

satisfiesas constを組み合わせた使い方について、こちらのブログで同期の龍ちゃんに解説を頼まれていたため、書いてみます!

今まで述べてきたように、satisfiesを使うと型のチェックを行うことができます。また、as const は使うことで、readonlyとすることができます。

龍ちゃんのブログの例を考えてみましょう。

as const satistfiesは以下のように使われています。

const colorCode = {
  main: 'bg-blue-400',
  error: 'bg-red-400',
} as const satisfies Record<string, string>;

分解して考えてみましょう。satisfiesだけの場合は、以下のように型のチェックは行われますが、プロパティを書き換えることができます。

const colorCode = {
  main: 'bg-blue-400',
  error: 'bg-red-400',
  second: 1234,// number型は許容されないのでエラー
}satisfies Record<string, string>;

colorCode.main = "hoge"// 書き換え可能

as const だけの場合は、readonlyとなりますが、違う型のプロパティでも定義できてしまいます。

const colorCode = {
  main: 'bg-blue-400',
  error: 'bg-red-400',
  second: 1234,// errorにならない
} as const;

colorCode.main = "hoge" // readonlyのためエラー

このように、satisfiesas constを組み合わせて使うことで、readonlyにしつつ、型チェックを行うことができます。

また、as constの他の効果として、型のwideningを防げるというのもあります。

as constをつけない場合、以下のように各プロパティの型の推論結果はstringとなっています。

as constをつけることで、以下のように、推論結果でより詳細に中身を確認することができます。詳細は型のwideningについて調べてみて下さい。

 

as constをつけると型の推論をより細かくできますが、as constだけでは型をチェックすることができません。また、型注釈をしてしまうと、以下のように推論結果が失われてしまいます。

そのため、型の推論結果を保ちつつ(元の型を変えずに)、型チェックができるsatisfiesと、as constを組み合わせるという使い方が、良しとされているようです。

まとめ

  • satisfiesは、型を変えずに、型チェックができる!
  • JavaScriptチックに書きたいときにも使える
  • 定数の宣言の時はsatisfies + as constを利用する!

おわりに

今回はTypeScriptのver.4.9で追加されたsatisfiesについてまとめてみました。

業務ではあまり使ってなかったのですが、今回まとめてみてメリットが結構あることが分かったので、今後は積極的に活用していきたいと思います。ほかにもこんなところで使えるよ!みたいなことを見つけたら改めて記事にしようと思います!

 

 

 

 

ご覧いただきありがとうございます! この投稿はお役に立ちましたか?

役に立った 役に立たなかった

0人がこの投稿は役に立ったと言っています。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です