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 のインストール チュートリアル

推薦する

MySQLでよく使われる文字列関数トップ10の詳細な説明

こんにちは、みんな!技術の話ばかりで髪は切らないトニーです。データベース関数は、何らかの機能を持ち、...

VMware Workstation での VMware vSphere のセットアップ (グラフィック チュートリアル)

VMware vSphere は、業界をリードする最も信頼性の高い仮想化プラットフォームです。 v...

CSS スタイルが機能しない (史上最も完全な解決策の概要)

ページを作成するときに、記述した CSS スタイルが有効にならないことがあります。この現象にはさまざ...

jsとcssのブロッキング問題の詳細な分析

目次DOMContentLoadedとロードjs ブロッキングとは何ですか? CSS ブロッキングと...

MySQL 5.7.18 MSI インストール グラフィック チュートリアル

この記事では、参考までにMySQL 5.7.18 MSIインストールチュートリアルを紹介します。具体...

Linux での Docker と portainer の設定方法

1.Docer CEをインストールして使用するこの記事では、CentOS 7 を例に Docker ...

VMware Workstation 14 Pro のインストールとアクティベーションのグラフィック チュートリアル

この記事では、VMware Workstation 14 Proのインストールとアクティベーションに...

Nginx_geo モジュールを使用して CDN スケジュールを設定する方法

NginxのGeoモジュールの紹介geo ディレクティブは、ngx_http_geo_module ...

よくある CSS のヒントと経験談 11 選

1. 画像の下にある数ピクセルの空白を削除するにはどうすればよいですか?コードをコピーコードは次のと...

JavaScript ではおそらく switch 文を使う必要はない

目次スイッチも複雑なコードブロックもありませんPythonからのインスピレーション辞書を使用してスイ...

MySQL 8.0.16 圧縮版のダウンロードと Win10 システムへのインストール チュートリアル

公式サイトからダウンロード: https://www.mysql.com MySQLの公式サイトにア...

mysqlは指定された期間内の統計データを取得します

mysqlは指定された期間内の統計データを取得します年別統計 選択 カウント(*)、 DATE_FO...

Alibaba Cloud ECS クラウド サーバー (Linux システム) は、MySQL をインストールした後にリモートで接続できません (落とし穴)

昨日、1年間使用していた Alibaba Cloud サーバーを購入しました。システムは Linux...

カルーセル効果を作成するためのjs

カルーセルはフロントエンド開発において比較的重要なポイントだと思います。ネイティブjsの知識ポイント...

InnoDB がトランザクション分離レベルを巧みに実装する方法

序文前回の記事「MySQL ロック メカニズムの詳細説明」では、InnoDB のロック メカニズムに...