SHOEISHA iD

※旧SEメンバーシップ会員の方は、同じ登録情報(メールアドレス&パスワード)でログインいただけます

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

ますます便利になるTypeScript! バージョン3からの変更点と総まとめ

TypeScriptバージョン3から5.2までのアップデート内容まとめ ──型の絞り込みについて解説

ますます便利になるTypeScript! バージョン3からの変更点と総まとめ 第4回

  • このエントリーをはてなブックマークに追加

 その登場以来、順当にアップデートを重ねているTypeScriptに関して、バージョン3から5.2までの変更点をまとめて紹介するのが、本連載です。前回は、TypeScriptの根幹ともいえる型システムに関する変更点を紹介しました。今回は、型の絞り込みに関する変更点を紹介します。

  • このエントリーをはてなブックマークに追加

制御フローと型の絞り込み

 本連載は、TypeScriptのバージョン3から5.2までのアップデート内容を、テーマごとにバージョン横断で紹介する連載です。第4回である今回紹介するのは、型の絞り込みに関する変更点です。まず、その型の絞り込みの確認から話を始めていきます。

型の絞り込みとは

 例えば、前回登場したPersonalBaseData型を値の型、数値をキーの型とするMapオブジェクトを用意し、リスト1のようなコードを考えたとします。

リスト1:Mapのget()の値はundefinedの可能性がある
const list = new Map<number, PersonalBaseData>();
  :
const key = 22454;
const item = list.get(key);
const name = item.name;  // (1)

 このコードは、(1)で図1のエラーとなります。このエラーメッセージの通り、Mapオブジェクトからget()した要素は、引数のキーのデータによっては取得対象の値が存在せず、undefinedとなる可能性があるからです。そして、このundefinedな変数のプロパティへのアクセスはエラーを招くため、TypeScriptでは事前にエラー表示となります。

図1:itemのnameプロパティへのアクセスでのエラー
図1:itemのnameプロパティへのアクセスでのエラー

 実際、get()とした値を格納した変数(リスト1ではitem)のデータ型をVS Codeで表示させると、図2のように、PersonalBaseDataとundefinedとのユニオン型となっています。

図2:itemのデータ型
図2:itemのデータ型

 このエラーを解決するために、リスト1の(1)の部分は、リスト2のようにundefinedかどうかのチェックを行います。

リスト2:Mapのget()した値はundefinedのチェックが必要
if(item != undefined) {
  const name = item.name;
}

 そして、TypeScriptでは、このようにundefinedの可能性を排除した条件ブロック内では、その変数(リスト2ではitem)は、図3のように、undefinedの型がなくなり、単なるPersonalBaseData型となっています。

図3:itemのデータ型からundefinedがなくなった状態
図3:itemのデータ型からundefinedがなくなった状態

 このように、if-else if-elseやswitchの条件分岐構文において、変数の型チェックを行うことを、「制御フロー分析(Control Flow Analysis)」といいます。そして、その制御フロー分析内で、条件に応じて型の可能性が排除されていくことを、「型の絞り込み(Type Narrowing)」といいます。また、型の絞り込みで行う条件部分(リスト2ならば「item != undefined」の部分)を「型ガード(Type Guards)」といいます。

型ガードの変数化

 この型ガード、すなわち、型の絞り込み条件は、()内に記述して初めて、制御フローブロック内で型の絞り込みが有効になっていました。これが、変数化しても絞り込みが有効になるようにバージョン4.4で変更されています。これを利用すると、リスト2は、リスト3のようなコードになります。

リスト3:型ガードを変数化したコード
  :
const item = list.get(key);
const isNotUndefined = (item != undefined);  // (1)
if(isNotUndefined) {  // (2)
  const name = item.name;  // (3)
}

 リスト3では、(1)で型ガードをisNotUndefinedと変数化しており、その変数isNotUndefinedを(2)の条件としています。それでも、型の絞り込みは行われ、(3)ではエラーが発生しません。

inによる型ガード

 型ガードを行う場合、「!= undefined/null」の他に、JavaScript由来のtypeof演算子やinstanceof演算子を利用した型チェックコードも、もちろん型ガードとして利用できます。ただし、typeof演算子は、プリミティブ型のチェックは行えますが、オブジェクトは単にobjectとしてチェックできるだけで、どのオブジェクトかまではチェックできません。また、instanceofはオブジェクトをnewしたものとの比較となります。ということは、オブジェクトリテラルの型チェックはできません。

 例えば、前回登場したPersonalBaseData型かPersonalBirthData型かの型ガードを行う場合は、typeofやinstanceofは使えず、代わりに、inを使います。inもJavaScript由来の演算子であり、そのオブジェクトに特定のプロパティが含まれているかをチェックする演算子です。これを利用すれば、リスト4のような制御フロー分析コードが記述できます。

リスト4:inを利用した制御フロー分析コード
function show(person: PersonalBaseData | PersonalBirthData): void {
  const name = person.name;  // (1)
  if("phone" in person) {  // (2)
    const phone = person.phone;  // (3)
      :
  }
  else {  // (4)
    const birth = person.birth;  // (5)
      :
  }
}

 リスト4の関数show()の引数personはPersonalBaseData型かPersonalBirthData型かのどちらかです。そのどちらもnameプロパティは存在しているので、(1)のコードは問題なく動作します。一方、(3)のようなphoneプロパティへのアクセスはPersonalBaseData型でなければエラーとなります。同様に、(5)のようなbirthプロパティへのアクセスはPersonalBirthData型でなければなりません。そこで、(2)や(4)のように制御フロー分析を行って、それぞれのブロック内で型の絞り込みが行われた上でアクセスするようにしています。

 その条件として利用しているのが、(2)のinです。(2)では、phoneプロパティがpersonに含まれているかを型ガード(型の絞り込み条件)としています。

未定義プロパティへのアクセスがinによって可能に

 このinによる型ガードの仕組みが、バージョン4.9でより強力になりました。例えば、あるサーバAPIエンドポイントから取得したJSONデータを表すインターフェースとしてリスト5のReturnJSONを考えます。statusはサーバ側の処理が成功したかどうかを表す数値として1(成功)か0(失敗)が格納されています。そして、その失敗か成功かでresultのデータ内容が変わってくるため、unknown型となっています。

リスト5:サーバAPIエンドポイントから取得したJSONデータを表すインターフェース
interface ReturnJSON {
  status: number;
  result: unknown;
}

 ここで、statusが1、つまり成功した場合、resultに格納されたオブジェクトのmsgプロパティを取得するコードを考えます。そのような関数をextractResult()としたならば、これは、例えば、リスト6のようなコードとなります。

リスト6:inによる未定義プロパティへのアクセスの有効化
function extractResult(returnJSON: ReturnJSON): string {
  let result = "通信失敗";
  if(returnJSON.status == 1) {
    let result = "データ取得失敗";
    if(returnJSON.result != null && typeof returnJSON.result == "object" && "msg" in returnJSON.result && typeof returnJSON.result.msg == "string") {  // (1)
      result = returnJSON.result.msg;  // (2)
    }
  }
  return result;
}

 リスト6で注目するのは、(2)です。ReturnJSON型のreturnJSONのresultプロパティは、リスト5の通り、unknown型です。となると、そのプロパティのmsgへのアクセスは、本来エラーとなるはずですが、リスト6ではなりません。その種明かしが、(1)の条件(型ガード)の「"msg" in returnJSON.result」の部分です。この条件に合致した時点で、resultプロパティ内にはmsgプロパティは存在すると判断し、図4のようにプロパティmsgが存在するオブジェクトとして扱われるようになります。

図4:プロパティmsgが存在することが型定義されたreturnJSON.result
図4:プロパティmsgが存在することが型定義されたreturnJSON.result

会員登録無料すると、続きをお読みいただけます

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

次のページ
制御フロー分析で重要な判別可能なユニオン型

この記事は参考になりましたか?

  • このエントリーをはてなブックマークに追加
ますます便利になるTypeScript! バージョン3からの変更点と総まとめ連載記事一覧

もっと読む

この記事の著者

WINGSプロジェクト 齊藤 新三(サイトウ シンゾウ)

WINGSプロジェクトについて>有限会社 WINGSプロジェクトが運営する、テクニカル執筆コミュニティ(代表 山田祥寛)。主にWeb開発分野の書籍/記事執筆、翻訳、講演等を幅広く手がける。2018年11月時点での登録メンバは55名で、現在も執筆メンバを募集中。興味のある方は、どしどし応募頂きたい。著書記事多数。 RSS Twitter: @yyamada(公式)、@yyamada/wings(メンバーリスト) Facebook<個人紹介>WINGSプロジェクト所属のテクニカルライター。Web系製作会社のシステム部門、SI会社を経てフリーランスとして独立。屋号はSarva(サルヴァ)。HAL大阪の非常勤講師を兼務。

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

山田 祥寛(ヤマダ ヨシヒロ)

静岡県榛原町生まれ。一橋大学経済学部卒業後、NECにてシステム企画業務に携わるが、2003年4月に念願かなってフリーライターに転身。Microsoft MVP for Visual Studio and Development Technologies。執筆コミュニティ「WINGSプロジェクト」代表。主な著書に「独習シリーズ(Java・C#・Python・PHP・Ruby・JSP&サーブレットなど)」「速習シリーズ(ASP.NET Core・Vue.js・React・TypeScript・ECMAScript、Laravelなど)」「改訂3版JavaScript本格入門」「これからはじめるReact実践入門」「はじめてのAndroidアプリ開発 Kotlin編 」他、著書多数

※プロフィールは、執筆時点、または直近の記事の寄稿時点での内容です

この記事は参考になりましたか?

この記事をシェア

  • このエントリーをはてなブックマークに追加
CodeZine(コードジン)
https://codezine.jp/article/detail/19331 2024/04/19 11:00

おすすめ

アクセスランキング

アクセスランキング

イベント

CodeZine編集部では、現場で活躍するデベロッパーをスターにするためのカンファレンス「Developers Summit」や、エンジニアの生きざまをブーストするためのイベント「Developers Boost」など、さまざまなカンファレンスを企画・運営しています。

新規会員登録無料のご案内

  • ・全ての過去記事が閲覧できます
  • ・会員限定メルマガを受信できます

メールバックナンバー

アクセスランキング

アクセスランキング