TypeScript 2.0 マーク付き共用体型の詳細な説明

TypeScript 2.0 マーク付き共用体型の詳細な説明

タグ付きユニオン型を使用した支払い方法の構築

システムのユーザーが選択できる次の支払い方法をモデル化するとします。

  • 現金
  • 指定されたメールアドレスでPayPal
  • 指定されたカード番号とセキュリティコードを持つクレジットカード

これらの支払い方法にはTypeScriptインターフェースを作成することができます

インターフェース キャッシュ {
  種類: "現金";
}

インターフェース PayPal {
  種類: "paypal",
  メールアドレス: 文字列;
}

インターフェース CreditCard {
  種類: "クレジット";
  カード番号: 文字列;
  セキュリティコード: 文字列;
}

必須情報に加えて、各タイプには種類属性、いわゆる判別属性があることに注意してください。ここでの各ケースは文字列リテラル型です。

ここで、先ほど定義した 3 つの型の結合である PaymentMethod 型を定義します。このように、PaymentMethod で宣言された各変数には、指定された 3 つのコンポーネント タイプのいずれかが必要です。

PaymentMethod = Cash | PayPal | CreditCard と入力します。

型が決まったので、支払い方法を受け入れて人間が読める発話を返す関数を記述しましょう。

関数describePaymentMethod(メソッド: PaymentMethod) {
  スイッチ (メソッド.種類) {
    ケース「現金」:
      // ここで、メソッドの型はCashです
      「現金」を返します。

    ケース「paypal」:
      // ここで、メソッドの型はPayPalです
      `PayPal (${method.email})` を返します。

    ケース「クレジット」:
      // ここで、メソッドはCreditCard型です
      `クレジットカード (${method.cardNumber})` を返します。
  }
}

まず、関数には型注釈がほとんどなく、メソッド パラメーター用の注釈が 1 つだけ含まれています。それ以外では、関数はほとんど純粋な ES2015 コードです。

switch ステートメントの各ケースにおいて、TypeScript コンパイラはユニオン型をそのメンバー型の 1 つに絞り込みます。たとえば、「paypal」に一致する場合、メソッド パラメータのタイプは PaymentMethod から PayPal に絞り込まれます。したがって、型アサーションを追加しなくても、 email プロパティにアクセスできます。

基本的に、コンパイラはプログラム制御フローをトレースして、タグ付き共用体型を絞り込みます。 switch ステートメントに加えて、条件と代入および戻りの効果も考慮します。

関数describePaymentMethod(メソッド: PaymentMethod) {
  if (method.kind === "cash") {
    // ここで、メソッドはCash型です
    「現金」を返します。
  }

  // ここで、メソッドの型は PayPal | CreditCard です

  if (method.kind === "paypal") {
    // ここで、メソッドの型はPayPalです
    `PayPal (${method.email})` を返します。
  }

  // ここで、メソッドはCreditCard型です
  `クレジットカード (${method.cardNumber})` を返します。
}

制御フロー型分析により、タグ付きユニオン型の使用が非常にスムーズになります。 TypeScript 構文のオーバーヘッドが最小限に抑えられているため、ほぼ純粋な JavaScript を記述でき、型チェックとコード補完のメリットも享受できます。

タグ付きユニオン型を使用した Redux アクションの構築

タグ付きユニオン型が真価を発揮するユースケースは、TypeScript アプリケーションで Redux を使用する場合です。 Todo アプリケーションのモデル、2 つのアクション、およびリデューサーを含む例を作成しましょう。

以下は、単一の ToDo を表す簡略化された Todo タイプです。ここでは、プロパティが変更されないようにするために、readonly 修飾子が使用されます。

インターフェースTodo {
  読み取り専用テキスト: 文字列;
  読み取り専用完了: ブール値;
}

ユーザーは新しい ToDo を追加したり、既存の ToDo の完了ステータスを切り替えることができます。これらの要件に応じて、次の 2 つの Redux 操作が必要です。

インターフェースAddTodo{
  タイプ: "ADD_TODO";
  テキスト: 文字列;
}

インターフェースToggleTodo {
  タイプ: "TOGGLE_TODO";
  インデックス: 番号
}

前の例と同様に、アプリケーションでサポートされているすべてのアクションの結合として Redux アクションを構築できるようになりました。

タイプ ReduxAction = AddTodo | ToggleTodo;

この場合、 type プロパティは判別属性として機能し、Redux の一般的な命名パターンに従います。次に、次の 2 つのアクションで動作する Reducer を追加します。

関数todosReducer(
  状態: ReadonlyArray<Todo> = [],
  アクション: ReduxAction
): ReadonlyArray<Todo> {
  スイッチ(アクションタイプ){
    ケース「ADD_TODO」:
      // ここでのアクションは AddTodo 型です
      [...state、{text: action.text、done: false }] を返します。

    ケース「TOGGLE_TODO」:
      // ここでのアクションはToggleTodo型です
      状態.map((todo, インデックス) => { を返します。
        if (インデックス !== action.index) {
          todo を返します。
        }

        戻る {
          テキスト: todo.text、
          完了: !todo.done
        };
      });

    デフォルト:
      状態を返します。
  }
}

同様に、関数シグネチャにのみ型注釈が含まれます。残りのコードは純粋な ES2015 であり、TypeScript 固有のものではありません。

前の例と同じロジックに従います。 Redux アクションの type プロパティに基づいて、既存の状態を変更せずに新しい状態を計算します。 switch ステートメントの場合、型アサーションなしで、各操作タイプに固有のテキストおよびインデックス プロパティにアクセスできます。

決してしないタイプ

TypeScript 2.0 では、新しいプリミティブ型 never が導入されました。 never 型は、その型の値が決して現れないことを示します。具体的には、never は決して戻らない関数の戻り値の型であり、型ガードで決して true にならない変数の型でもあります。

これらは、以下に説明する never 型の正確な特性です。

  • Never はすべてのタイプのサブタイプであり、すべてのタイプに割り当てることができます。
  • どの型も never のサブタイプではなく、また never に代入可能でもありません (never 型自体を除く)。
  • 関数式または矢印関数に戻り値の型注釈がない場合、関数に return ステートメントがない場合、または never 型の return ステートメントのみがある場合、および関数がエンドポイントに対して実行可能でない場合 (たとえば、制御フロー分析によって判断される場合)、関数の推論される戻り値の型は never になります。
  • 明示的な never return 型アノテーションを持つ関数では、すべての return ステートメント (存在する場合) に never 型の式が含まれる必要があり、関数のエンドポイントは実行可能であってはなりません。

聞いたことに戸惑っています。次に、このビッグブラザーNeverについて、いくつかの例を挙げてお話しします。

決して戻らない関数

以下は、決して返されない関数の例です。

// タイプ () => never
const sing = 関数() {
  (真)の間{
    console.log("値を返さないだけなのに、何が問題なの!");
    console.log("値を返さないだけなのに、何が問題なの!");
    console.log("値を返さないだけなのに、何が問題なの!");
    console.log("値を返さないだけなのに、どうしたの!");
    console.log("値を返さないだけなのに、どうしたの!");
    console.log("値を返さないだけなのに、どうしたの!");
  }
}

この関数は、break または return ステートメントを含まない無限ループで構成されているため、ループから抜け出す方法はありません。したがって、関数の推論された戻り値の型は never です。

同様に、次の関数の戻り値の型は、neverであると推論されます。

// タイプ (メッセージ: 文字列) => never
const failwith = (メッセージ: 文字列) => {
  新しいエラー(メッセージ)をスローします。
};

TypeScript は、関数に戻り値の型注釈も到達可能なエンドポイントもないため (制御フロー分析によって決定)、never 型を推論します。

このタイプの変数を持つことはできません

あるいは、never 型は決して true にならないと推論されます。次の例では、値パラメータが文字列と数値の両方であるかどうかをチェックしていますが、これは不可能です。

関数は不可能なTypeGuard(値: 任意) {
  もし (
    typeof 値 === "文字列" &&
    typeof 値 === "数値"
  ){
    value; // 型 never
  }
}

この例は明らかに過度に不自然ですので、より実用的な使用例を見てみましょう。次の例は、型ガード下の変数のユニオン型を絞り込む TypeScript の制御フロー分析を示しています。直感的に、型チェッカーは、値が文字列であることを確認すると、それが数値になることはできず、その逆も同様であることを認識します。

関数 controlFlowAnalysisWithNever(
  値: 文字列 | 数値
){
  if (typeof value === "文字列") {
    value; // 文字列型
  } そうでない場合 (typeof value === "number") {
    value; // 数値型
  } それ以外 {
    value; // 型 never
  }
}

最後の else ブランチでは、値を文字列または数値にすることはできないことに注意してください。この場合、TypeScript は、値パラメータに string | number 型として注釈を付けているため never 型を推論します。つまり、値パラメータには、文字列または数値以外の型を指定することはできません。

制御フロー分析によって文字列と数値が値の型の候補から除外されると、型チェッカーは残っている唯一の可能性である never 型を推論します。ただし、値の型が never であるため、値を使用して有用な操作を行うことはできません。そのため、エディター ツールは値に使用できるメソッドまたはプロパティを自動的に表示しません。

never と void の違い

TypeScript にはすでに void 型があるのに、なぜ never 型が必要なのかと疑問に思うかもしれません。これら 2 つは似ているように見えるかもしれませんが、異なる概念です。

明示的な戻り値のない関数は暗黙的に undefined を返します。通常、このような関数は「何も返さない」と言われますが、実際には返されます。このような場合、通常は戻り値は無視されます。このような関数は、TypeScript では void 戻り値の型を持つと推論されます。

never return 型の関数は決して戻りません。また、undefined も返されません。関数は正常に完了しません。つまり、エラーがスローされるか、実行がまったく完了しません。

関数宣言の型推論

関数宣言の戻り値の型の推論に関して小さな問題があります。先ほど挙げたいくつかの「絶対にやってはいけない」特徴を見てみると、次の文が見つかります。

関数式または矢印関数に戻り値の型注釈がない場合、関数に return ステートメントがない場合、または never 型の return ステートメントのみがある場合、および関数がエンドポイントに対して実行可能でない場合 (たとえば、制御フロー分析によって判断される場合)、関数の推論される戻り値の型は never になります。

関数式と矢印関数については言及していますが、関数宣言については言及していません。つまり、関数式に対して推論される戻り値の型は、関数宣言に対して推論される戻り値の型とは異なる場合があります。

// 戻り値の型: void
関数 failwith1(メッセージ: 文字列) {
  新しいエラー(メッセージ)をスローします。
}

// 戻り値の型: なし
const failwith2 = 関数(メッセージ: 文字列) {
  新しいエラー(メッセージ)をスローします。
};

この動作の理由は、以下に説明するように、下位互換性のためです。関数宣言の戻り値の型を never にしたい場合は、明示的に注釈を付けることができます。

関数 failwith1(メッセージ: 文字列): 決して {
  新しいエラー(メッセージ)をスローします。
}

以上がTypeScript 2.0のタグ付き共用体型の詳しい説明です。TypeScript 2.0のタグ付き共用体型の詳細については、123WORDPRESS.COMのその他の関連記事もご覧ください。

以下もご興味があるかもしれません:
  • Typescript での infer キーワードの使用に関する詳細な理解
  • TypeScript の Enum が問題となる理由
  • TypeScript をインストール、使用、自動コンパイルする方法に関するチュートリアル
  • Vue の新しいパートナー TypeScript クイックスタート実践記録
  • TypeScript でオブジェクト キーの値の範囲を制限する方法
  • TypeScript のマップされた型とより優れたリテラル型推論について説明します。
  • TypeScript 3.7 で注目すべき 3 つの新機能について簡単に説明します。
  • TypeScript 関数の定義と使用例のチュートリアル

<<:  MySQLの文字列インターセプト関連関数の概要

>>:  CentOS7.4 に MySQL 5.7.26 をインストールするための詳細なチュートリアル

推薦する

border-radiusは要素に丸い境界線を追加する方法です

border-radius:10px; /* すべての角は半径 10px で丸められます*/ bor...

Linux に MySql 5.7.21 をインストールするための詳細な手順

序文Linux で最も広く使用されているデータベースは MySQL です。この記事では、Linux ...

MySQL 文字セットの概要

目次文字セット比較ルール4つのレベルの文字セットと比較規則3つのシステム変数このノートは主にMySQ...

MySQLの高性能最適化スキルの概要

データベースコマンド仕様すべてのデータベース オブジェクト名には小文字を使用し、アンダースコアで区切...

Nginx でバージョン番号を隠す方法

Nginx はバージョン番号を非表示にする実稼働環境では、セキュリティ上の脆弱性の漏洩を避けるために...

WeChat アプレット学習 WXS 使用方法チュートリアル

wxsとは何ですか? wxs (WeiXin Script) は、小規模プログラム用のスクリプト言語...

JavaScript をスリープまたは待機させる方法

目次概要setTimeout() の確認スリープ関数の書き方シンプルな選択ループで実行されますか?要...

自動ヘルスレポートを実現するDocker+Selenium方式

この記事では、ある大学の健康報告システムを例に、Web 側の自動化操作を完成させます。使用したテクノ...

システム外のフォント参照とトランジション効果

コードをコピーコードは次のとおりです。 <span style="font-fami...

nodejs + koa + typescript の統合と自動再起動に関する問題

目次バージョンノートプロジェクトを作成する依存関係をインストールするコンテンツの記入src/serv...

Vueの使用に関する深い理解

目次Vueのコアコンセプトを理解するVueの双方向バインディングの原理と実装を探るVue 双方向バイ...

JavaScript タイマー原理の詳細な説明

目次1. setTimeout() タイマー2. setTimeout() タイマーを停止する3. ...

この記事では、Vueのフロントエンドページングとバックエンドページングを実装する方法を説明します。

目次1: フロントエンドの手書きページング(データ量が少ない場合) 2: バックエンドのページング、...

Tomcat が応答データグラムを書き戻すタイミングの詳細な分析

疑問が生じるこの質問は、ファイルのダウンロードを記述しているときに発生しました。HttpServle...

Linux でファイルをあいまい検索するのに適したコマンドは何ですか?

1. はじめにこの記事では、主に Linux システムでコマンドライン ツールを使用してファイルを...