TypeScript のマップされた型とより優れたリテラル型推論について説明します。

TypeScript のマップされた型とより優れたリテラル型推論について説明します。

概要

TypeScript 2.1 では、型システムに強力な追加機能であるマップ型が導入されました。基本的に、マップされた型を使用すると、属性型をマップすることによって既存の型から新しい型を作成できます。指定したルールに従って、既存の型の各プロパティを変換します。変換された属性によって新しいタイプが形成されます。

マップされた型を使用すると、型システム内の Object.freeze() などのメソッドの効果をキャプチャできます。オブジェクトが凍結されると、そのオブジェクトのプロパティを追加、変更、または削除することはできなくなります。マップされた型を使用せずに、これを型システムでどのようにエンコードできるかを見てみましょう。

インターフェースポイント{
  x: 数値;
  y: 数値;
}

インターフェース FrozenPoint {
  読み取り専用 x: 数値;
  読み取り専用 y: 数値;
}

関数freezePoint(p: Point): FrozenPoint {
  Object.freeze(p) を返します。
}

const origin = freezePoint({ x: 0, y: 0 });

// エラー! 'x' には代入できません。
// は定数または読み取り専用プロパティです。
原点.x = 42;

x と y の 2 つのプロパティを含む Point インターフェイスを定義します。また、すべてのプロパティが readonly を使用して読み取り専用プロパティとして定義されている点を除いて Point と同じである別のインターフェイス FrozenPoint も定義します。

freezePoint 関数は、Point を引数として受け入れてそれをフリーズし、同じオブジェクトを呼び出し元に返します。ただし、オブジェクトの型は FrozenPoint に変更されているため、そのプロパティは読み取り専用として静的に型指定されます。そのため、TypeScript では x プロパティに 42 を割り当てようとするとエラーが発生します。実行時に、割り当ては TypeError をスローするか (厳密モード)、または何もせずに失敗します (非厳密モード)。

上記の例はコンパイルされて正しく動作しますが、2 つの大きな欠点があります。

  • 2 つのインターフェースが必要です。 Point 型に加えて、両方のプロパティに readonly 修飾子を追加できるように、FrozenPoint 型も定義する必要があります。 Point を変更すると、FrozenPoint も変更する必要があり、エラーが発生しやすく、面倒です。
  • freezePoint関数が必要です。アプリケーションで凍結するオブジェクトのタイプごとに、そのタイプのオブジェクトを受け入れ、凍結されたタイプのオブジェクトを返すラッパー関数を定義する必要があります。マッピングされた型がないと、Object.freeze() を汎用的に静的に使用することはできません。

マップされた型を使用して Object.freeze() を構築する

lib.d.ts ファイルで Object.freeze() がどのように定義されているかを見てみましょう。

/**
  * 既存のプロパティ属性と値の変更を防止し、新しいプロパティの追加を防止します。
  * @param o 属性をロックするオブジェクト。
  */
フリーズ<T>(o: T): 読み取り専用<T>;

このメソッドの戻り値の型は Readonly<T> です。これはマッピング型であり、次のように定義されます。

型 Readonly<T> = {
  読み取り専用 [P in keyof T]: T[P]
};

この構文は最初は難しいかもしれませんので、ステップごとに分解してみましょう。

  • ジェネリック Readonly は、T という名前の型パラメータを使用して定義されます。
  • 角括弧内では、keyof 演算子が使用されます。 keyof T は、型 T のすべてのプロパティ名を文字列リテラル型の結合として表します。
  • 角括弧内の in キーワードは、マップされた型を扱っていることを示します。 [P in keyof T]: T[P]は、型Tの各プロパティPの型をT[P]に変換することを意味します。 readonly 修飾子がない場合、これは恒等キャストになります。
  • 型 T[P] は、型 T のプロパティ P の型を表すルックアップ型です。
  • 最後に、readonly 修飾子は、各プロパティを読み取り専用プロパティに変換することを指定します。

Readonly<T> 型はジェネリックなので、T に提供するすべての型は Object.freeze() で正しくラップされます。

const origin = Object.freeze({ x: 0, y: 0 });

// エラー! 'x' には代入できません。
// は定数または読み取り専用プロパティです。
原点.x = 42;

マッピングタイプの構文はより直感的である

今回は Point 型を例にして、型マッピングの仕組みを大まかに説明します。以下は説明のみを目的としており、TypeScript で使用される解析アルゴリズムを正確に反映するものではないことに注意してください。

型エイリアスから始めます:

ReadonlyPoint を Readonly<Point> と入力します。

これで、Readonly<T> のジェネリック型 T を Point 型に置き換えることができます。

タイプ ReadonyPoint = {
  読み取り専用 [Point のキーの P]: Point[P]
};

T が Point であることがわかったので、keyof Point で表される文字列リテラル型の結合を決定できます。

タイプ ReadonlyPoint = {
  読み取り専用 [P in "x" | "y"]: ポイント[p]
};

型 P は各プロパティ x と y を表します。マップされた型の構文を削除して、これらを個別のプロパティとして記述してみましょう。

タイプ ReadonlyPoint = {
  読み取り専用x: Point["x"];
  読み取り専用y: Point["y"];
};

最後に、2 つのルックアップ タイプを解析し、どちらも数値である具体的な x 型と y 型に置き換えることができます。

タイプ ReadonlyPoint = {
  読み取り専用 x: 数値;
  読み取り専用 y: 数値;
};

最終的に、結果の ReadonlyPoint 型は、手動で作成した FrozenPoint 型と同じになります。

マッピングタイプのその他の例

上記の lib.d.ts ファイルでは、組み込みの Readonly<T> 型をすでに確認しました。さらに、TypeScript はさまざまな状況で役立つ他のマップされた型を定義します。次のように:

/**
 * T内のすべてのプロパティをオプションにする
 */
型 Partial<T> = {
  [TのキーのP]?: T[P]
};

/**
 * TからプロパティセットKを選択する
 */
型 Pick<T, K は keyof T> を拡張します = {
  [KのP]: T[P]
};

/**
 * 型TのプロパティKのセットを持つ型を構築する
 */
型 Record<K extends string, T> = {
  [P in K]: T
};

以下にマッピング タイプの例を 2 つ示します。必要に応じて独自のマッピング タイプを記述することもできます。

/**
 * T内のすべてのプロパティをnull可能にする
 */
Nullable<T>型 = {
  [T のキーの P]: T[P] | null
};

/**
 * Tのすべてのプロパティを文字列に変換する
 */
型Stringify<T> = {
  [TのキーのP]: 文字列
};

マップされた型と共用体の組み合わせも興味深いです。

タイプ X = Readonly<Nullable<Stringify<Point>>>;
// タイプ X = {
// 読み取り専用 x: string | null;
// 読み取り専用 y: string | null;
// };

マッピングタイプの実際の使用例

マッピング型は実際によく見られます。React と Lodash を見てみましょう。

  • react:component の setState メソッドを使用すると、状態全体またはそのサブセットを更新できます。必要な数のプロパティを更新できるため、setState メソッドは Partial<T> の優れた使用例となります。
  • Lodash: pick 関数は、オブジェクトからプロパティのセットを選択します。このメソッドは、選択したプロパティのみを含む新しいオブジェクトを返します。この動作は、その名前が示すように Pick<T> を使用して構築できます。

リテラルの型推論の改善

以前は、文字列、数値、ブール値のリテラル型 (例: "abc"、1、true) は、明示的な型注釈が存在する場合にのみ推論されていました。 TypeScript 2.1 以降では、リテラル型には常にデフォルト値があると推測されます。 TypeScript 2.0 では、いくつかの新しいリテラル型が追加され、型システムが拡張されました。

  • ブールリテラル型
  • 数値リテラル
  • 列挙リテラル

型注釈のない const 変数または読み取り専用プロパティの型は、リテラル初期化子の型であると推論されます。型注釈のない初期化された let 変数、var 変数、パラメーター、または非読み取り専用プロパティの型は、初期値の拡張リテラル型であると推論されます。文字列リテラルの拡張型は string 、数値リテラルの拡張型は number 、true または false リテラルの拡張型は boolean 、列挙リテラルの拡張型は enumeration です。

より優れたconst変数推論

ローカル変数と var キーワードから始めましょう。 TypeScript は次の変数宣言を見ると、baseUrl 変数が文字列型であると推測します。

var baseUrl = "https://example.com/";
// 推論された型: 文字列

letキーワードで宣言された変数についても同様です。

baseUrl = "https://example.com/" とします。
// 推論された型: 文字列

どちらの変数もいつでも変更される可能性があるため、文字列型であると推測されます。これらはリテラル文字列値で初期化されますが、後で変更することもできます。

ただし、const キーワードを使用して変数を宣言し、文字列リテラルで初期化すると、推論される型は文字列ではなくリテラル型になります。

const baseUrl = "https://example.com/";
// 推論された型: "https://example.com/"

定数文字列変数の値は変わらないため、推論される型はより具体的になります。 baseUrl 変数には、「https://example.com/」以外の値を保持することはできません。

リテラル型推論は他のプリミティブ型にも機能します。定数を直接数値またはブール値で初期化すると、リテラル型が推論されます。

定数 HTTPS_PORT = 443;
// 推論された型: 443

定数 rememberMe = true;
// 推論された型: true

同様に、初期化子が列挙値の場合、リテラル型が推論されます。

列挙型FlexDirection{
  行、
  カラム
}

const 方向 = FlexDirection.Column;
// 推論された型: FlexDirection.Column

方向型は列挙リテラル型である FlexDirection.Column であることに注意してください。 let または var キーワードを使用して方向変数を宣言する場合、その推論された型は FlexDirection である必要があります。

読み取り専用プロパティの推論の改善

ローカル const 変数と同様に、リテラル初期化子を持つ読み取り専用プロパティもリテラル型であると推論されます。

クラスApiClient {
  プライベート読み取り専用 baseUrl = "https://api.example.com/";
  // 推論された型: "https://api.example.com/"

  get(エンドポイント: 文字列) {
    // ...
  }
}

読み取り専用クラス プロパティは、すぐにまたはコンストラクター内でのみ初期化できます。他の場所で値を変更しようとすると、コンパイル時エラーが発生します。したがって、読み取り専用クラス プロパティの値は変更されないため、そのリテラル型を推測することは合理的です。

もちろん、TypeScript は実行時に何が起こるかわかりません。readonly でマークされたプロパティは、JavaScript コードによっていつでも変更される可能性があります。 readonly 修飾子は、TypeScript コードからのプロパティへのアクセスのみを制限し、実行時には何も行いません。つまり、コンパイル中に削除され、生成された js コードには表示されません。

推論されたリテラル型の有用性

const 変数と読み取り専用プロパティのリテラル型を推論することがなぜ便利なのか疑問に思うかもしれません。次のコードを考えてみましょう。

const HTTP_GET = "GET"; // 推論された型: "GET"
const HTTP_POST = "POST"; // 推論された型: "POST"

関数 get(url: 文字列、メソッド: "GET" | "POST") {
  // ...
}

取得("https://example.com/", HTTP_GET);

HTTP_GET 定数の推論された型が "GET" ではなく文字列だった場合、HTTP_GET を get 関数の 2 番目の引数として渡すことができないため、コンパイル時エラーが発生します。

'string' 型の引数は、'"GET" | "POST" 型のパラメータに割り当てることはできません。

もちろん、対応する引数が 2 つの特定の文字列値のみを許可する場合、関数の引数として任意の文字列を渡すことはできません。ただし、2 つの定数に対してリテラル型「GET」と「POST」が推論されると、すべて正常に動作します。

以上がTypeScriptのマッピング型とリテラル型推論の改善についての詳しい説明です。TSの詳細については、123WORDPRESS.COMのその他の関連記事もご覧ください。

以下もご興味があるかもしれません:
  • TypeScript マッピング型の詳細

<<:  Linux で MySQL スケジュールタスクを実装する方法

>>:  Centos7 への mysql8.0rpm のインストール チュートリアル

推薦する

アルバムと写真をアルバムに保存するためのWeChatアプレット

私は現在、Xiao Nian Gao に似たビデオおよびツール アプリを開発しています。ユーザーが作...

Vueは小さな天気予報アプリケーションを実装します

これは私が Vue フレームワークを独学していたときに真似したウェブサイトです。いくつかの都市の天気...

ウェブサイトのテキストはまだデザインする必要がありますか?

多くの人が、ウェブサイト上のテキストはデザインする必要があるのか​​と疑問に思うかもしれません。多く...

Vueのウェブページスクリーンショット機能の詳しい説明

最近、プロジェクトで写真をアップロードする要件があるのですが、顧客がアップロードする写真のサイズがま...

Linux の daily_routine サンプルコードの詳細な説明

まずサンプルコードを見てみましょう: #/bin/bash cal 日付 -u echo "...

Docker - コンテナマウントディレクトリを変更する3つの方法のまとめ

方法 1: 設定ファイルを変更する (docker サービスを停止する必要があります) 1. doc...

HTML ページ共通スタイル (推奨)

以下のように表示されます。 XML/HTML コードコンテンツをクリップボードにコピーbody、di...

CocosCreatorでシューティングゲームを作る詳しい解説

目次シーン設定ゲームリソース砲塔の回転動的に生成された弾丸衝突計算効果を高めるターゲットの動き弾薬庫...

JavaScript キャンバスで動的な点と線の効果を実現

この記事では、動的な点と線の効果を実現するためのJavaScriptキャンバスの具体的なコードを参考...

Linux コマンドラインのクイックヒント: ファイルの検索方法

私たちのコンピューターには、ディレクトリ、写真、ソース コードなどのファイルが保存されています。たく...

MySQL ジョイントテーブル更新デー​​タの詳細な例

1.MySQL UPDATE JOIN構文MySQL では、UPDATE ステートメントでJOIN句...

面接で聞かれる可能性のあるCSSに関する質問

この記事は、100 回書かれ、質問された CSS の質問を記念するためのものです。聞く: CSS セ...

バントリストコンポーネントをスクロールしても、スクロールバーの位置は保持されます。

バントリストコンポーネントをスクロールするときに、スクロールバーの位置が保持されます。これは、kee...

vuex での Getter の使用法の詳細な説明

序文Vuex を使用すると、ストア内に「ゲッター」を定義できます (これはストアの計算されたプロパテ...

HTML+CSS+JavaScript でガールフレンド版のスクラッチ カードを作成します (一度見ればすぐに覚えられます)

誰もがスクラッチ チケットで遊んだことがあると思います。子供の頃、ポケットにお金が入るとすぐに友達に...