magazine off

TypeScriptのType GuardsとType Predicatesをそれぞれ適切に使う方法

投稿日 : 2021年4月21日(水曜日)

 

TypescriptのType Predicatesを理解してみようということで、

 

海外の記事を色々と探しておりました。

 

そうしましたら、TypeScript Type Guards and Type Predicates という記事を見つけまして、

 

なんとなく役に立ちそうだったので、これを翻訳しながら、分かりやすく、かいつまんで、まとめてお届けしたいと思います。

 

もし詳しく、全文を読みたければ、是非とも英語の記事を読んでみてください。

 

How to use Type Guards to narrow types in TypeScript.
TypeScript Type Guards and Type Predicates - DEV Community

 

TypeScriptタイプガードとタイプPredicates

 

ユニオン型は、複数の異なる型のパラメータを受け入れることを可能にします。

 

x型またはy型のいずれかを呼び出し時に選べるわけですが、

 

正確な型に基づいて何らかのコードを実行したいと思うかもしれません。

 

ここで、タイプガードとタイププレディケートが登場します。

 

まずは、型の宣言から始めましょう。

 

私は何かを説明しようとするとき、コードを見るのが好きです。

 

そうすることで、コンセプトをよりよく理解できるからです。

 

ブログを作っていて、2つの型があり、それが1つのユニオンを形成しているとします。

 

type Article = {
  frontMatter: Record<string, unknown>;
  content: string;
}

type NotFound = { 
  notFound: true;
}

type Page = Article | NotFound;

 

具体的な型としては、ArticleとNotFoundがあります。

 

目標は、ページをレンダリングする関数を書くことです。

 

ブログが存在するかどうかをチェックする要件や、notFound関数をいつ呼び出すかなどの詳細については触れませんが、

 

単一のレンダリング関数があると想像してみてください。

 

データベースの内容に基づいて、記事をレンダリングするか、not foundページをレンダリングします。

 

こんな感じです。

 

function handleRequest(slug: string): Page {
  const article = db.articles.findOne({ slug });
  const page = article ?? { notFound: true };
  return render(page);
}

 

ここでの課題は、

 

handleRequestがArticleとNotFoundのどちらを返したかを知る必要がある場合です。

 

JavaScriptでは、次のようなものを使います。

 

function render(page: Page) {
  if (page.content) {
    return page.content;
  }

  return '404 — not found';
}

 

しかし、TypeScriptではそれはうまくいきません。

 

プロパティの内容がPage型に存在しないことを示すErrorが投げられます。

 

 

Property 'content' does not exist on type 'Page'.
Property 'content' does not exist on type 'NotFound'.

 

これは、ユニオン内のすべての型にそのプロパティが含まれているわけではないからです。

 

これを解決するには、タイプガードを追加する必要があります。

 

Type Guard

 

タイプガードとは、現在のスコープ内の型を保証する実行時のチェックを行う式のことです。

 

手っ取り早い方法は、page.contentのチェックをTypeScriptが理解できるものに置き換えることだ。

 

 

function render(page: Page) {
  if ('content' in page) {
    return page.content;
  }

  return '404 — not found';
}

 

これは効果がありますが、メンテナンス性を犠牲にしています。

 

TypeScriptの利点は、使用されているプロパティを削除したときに警告してくれることです。

 

今回の変更により、例えばcontentプロパティの名前をbodyに変更しても、

 

TypeScriptは警告を出しませんし、あるいは、’content’にタイプミスがあったときにも警告話です。

 

そして、これがタイププレディケートが面白い理由です。

 

Type Predicate

 

型述語とは、次のような関数の戻り値の型のことです。

 

function isArticle(page: Page): page is Article {
  return 'content' in page;
}

 

Type Predicateはpage is Articleです。

 

また、知っておいていただきたいのですが、page in ‘content’はこの文脈ではタイプガードではありません。

 

これは単純な表現です。

 

タイプガードとは、TypeScriptに型を絞らせるif文のことです。

 

つまり、上の関数は以前のタイプガードとよく似ていて、同じようにメンテナンス性の問題を抱えています。

 

しかし、それを抽出したからには、それを解決することもできます。

 

function isArticle(page: Page): page is Article {
  return typeof (page as Article).content !== 'undefined';
}

 

これは、Articleをリファクタリングしてcontentプロパティを削除するとエラーになります。

 

Type Predicateとして宣言された関数は、ブール値を返さなければなりません。

 

戻り値がtrueの場合、TypeScriptは戻り値の型がType Predicateで宣言されているものであると仮定し、

 

TypeScriptは提供された引数ページがArticle型であると仮定します。

 

このメソッドをrender関数内で呼び出すと、こんな感じになる。

 

function render(page: Page) {
  if (isArticle(page)) {
    return page.content;
  }

  return '404 — not found';
}

 

TypeScript は page.content が存在することを知っています。

 

なぜなら if スコープ内では page は Article 型だからです。if (isArticle(page)) 式は、タイプガードです。

 

if文の後、pageはArticle型ではないならば、

 

ユニオンには2つの型しかないので、TypeScriptはその段階でNotFound型でなければならないことも認識しているということですね。

 

以上!とても勉強になりました。

 

How to use Type Guards to narrow types in TypeScript.
TypeScript Type Guards and Type Predicates - DEV Community

Categories

Recent Posts

rspecでprivateメソッドをテストする方法

  下記のような、文字列を二つ受け取って、間にセミコロンを結合して返すと…

投稿日 : 2019年10月9日(水曜日)

ゲーミング チェア 組み立て不要 or 簡単な椅子を発見したので紹介【おすすめ】

     ゲーミングチェアの組み立てが面倒臭い問題 &n…

投稿日 : 2021年2月5日(金曜日)

Swift(Perfect)でフォームからGET/POSTリクエスト

  httpd.confを探して、httpd.confに下記の情報を追記する…

投稿日 : 2018年7月9日(月曜日)

エンジニアが素人に技術的な説明をするとき、まるで自分が宇宙人であるかの如く相手に何も伝わらないという画像がSNSで話題

   この前、SNSで回ってきた画像が秀逸だったので紹介します。 …

投稿日 : 2021年10月12日(火曜日)