#TypeScript の型マッピング

導入

マッピングとは、通常はオブジェクト タイプのマッピング ルールに従って、あるタイプを別のタイプに変換することを指します。

たとえば、「A」タイプと「B」タイプがあります。

タイプ A = {
  foo: 数値;
  バー: 数値;
};

タイプ B = {
  foo: 文字列;
  バー: 文字列;
};

上記の例では、2 つのタイプの属性構造は同じですが、属性のタイプが異なります。属性が多数ある場合、一つ一つ記述するのは面倒です。

型マッピングを使用すると、型「A」から型「B」を取得できます。

タイプ A = {
  foo: 数値;
  バー: 数値;
};

タイプ B = {
  [keyof A のプロパティ]: 文字列;
};

上記の例では、タイプ B はプロパティ名インデックスの書き込み方法を採用しています。[prop in keyof A] は、タイプ A のすべてのプロパティ名を順番に取得し、各プロパティのタイプを に変更することを意味します。文字列

構文的には、[prop in keyof A] はプロパティ名式であり、ここでのプロパティ名を計算する必要があることを示します。具体的な計算ルールは次のとおりです。

  • prop: 属性名変数。名前は任意です。
  • in: 演算子。右側の共用体型の各メンバーを削除するために使用されます。
  • keyof A: タイプ A の各属性名を返し、共用体タイプを形成します。

以下はプリミティブ型をコピーする例です。

タイプ A = {
  foo: 数値;
  バー: 文字列;
};

タイプ B = {
  [keyof A の prop]: A[prop];
};

上の例では、タイプ「B」はタイプ「A」を変更せずにコピーします。

コードの再利用性を高めるために、一般的に使用されるマッピングをジェネリックとして記述することができます。

type ToBoolean<Type> = {
  [keyof Type のプロパティ]: ブール値;
};

上記の例では、他のオブジェクトのすべての属性値をブール型に変更できるジェネリック型が定義されています。

別の例を示します。

タイプ MyObj = {
  [P in 0|1|2]: 文字列;
};

// と同等
タイプ MyObj = {
  0: 文字列。
  1: 文字列。
  2: 文字列。
};

上記の例では、共用体型 '0|1|2' が 3 つの属性名にマッピングされます。

共用体型を使用せずに、属性名のマッピングに特定の型を直接使用することもできます。

タイプ MyObj = {
  ['foo' の p]: 数値;
};

// と同等
タイプ MyObj = {
  foo: 数値;
};

上の例では、「p in 'foo'」はメンバを1つだけ持つ共用体型とみなすことができるので、この1つの属性だけを持つオブジェクト型が得られます。

p in string と書くこともできます。

タイプ MyObj = {
  [文字列内のp]: ブール値;
};

// と同等
タイプ MyObj = {
  [p: 文字列]: ブール値;
};

上記の例では、[p in string]が属性名インデックス形式[p: string]のマッピング記述方法となります。

マッピングを通じて、オブジェクトのすべてのプロパティをオプションのプロパティに変更できます。

タイプ A = {
  a:文字列。
  b: 数字。
};

タイプ B = {
  [keyof A のプロパティ]?: A[プロパティ];
};

上記の例では、タイプ「B」はタイプ「A」のすべてのプロパティ名の後に疑問符を追加し、これらのプロパティをオプションにします。

実際、TypeScript の組み込みツール タイプ Partial<T> はこの方法で実装されています。

TypeScript の組み込みツール タイプ Readonly<T> は、すべてのプロパティを読み取り専用プロパティに変更できます。これもマッピングを通じて実装されます。

// T のすべてのプロパティを読み取り専用プロパティに変更します
type Readonly<T> = {
  readonly [P in keyof T]: T[P];
};

使い方は以下の通りです。

type T = { a: 文字列; b: 数値 };

タイプ ReadonlyT = Readonly<T>;
// {
// 読み取り専用 a: 文字列;
// 読み取り専用 b: 数値;
// }

マッピング修飾子

マッピングは、元のオブジェクトのオプションの読み取り専用プロパティを変更せずにコピーします。

タイプ A = {
  ?: 文字列;
  読み取り専用 b: 数値;
};

タイプ B = {
  [keyof A のプロパティ]: A[Prop];
};

// と同等
タイプ B = {
  ?: 文字列;
  読み取り専用 b: 数値;
};

上記の例では、タイプ「B」はタイプ「A」のマッピングであり、「A」のオプションのプロパティと読み取り専用のプロパティの両方を保持しています。

オプションの読み取り専用機能を削除するのはあまり便利ではありません。この問題を解決するために、TypeScript は 2 つのマッピング修飾子を導入しました。これらは、マッピング中に特定のプロパティの ? 修飾子と readonly 修飾子を追加または削除するために使用されます。

  • + 修飾子: +? または +readonly と記述し、マッピング属性に ? 修飾子または readonly 修飾子を追加します。
  • 修飾子: -? または -readonly として記述され、マップされたプロパティの ? 修飾子または readonly 修飾子を削除します。

以下は、オプションの属性を追加または削除する例です。

//オプションの属性を追加します
type オプション<タイプ> = {
  [Keyof Type の Prop]+?: Type[Prop];
};

// オプションの属性を削除します
type Concrete<Type> = {
  [Keyof Type の Prop]-?: Type[Prop];
};

属性名の後に「+?」または「-?」を記述する必要があることに注意してください。

以下は、読み取り専用プロパティを追加または削除する例です。

// 読み取り専用を追加します
type CreateImmutable<Type> = {
  +readonly [keyof Type の Prop]: Type[Prop];
};

// 読み取り専用を削除します
type CreateMutable<Type> = {
  -readonly [keyof Type の Prop]: Type[Prop];
};

+readonly-readonly は属性名の前に記述する必要があることに注意してください。

?readonlyの2つの修飾子を同時に追加または削除すると、次のように記述されます。

// 増加
type MyObj<T> = {
  +readonly [T のキーの P]+?: T[P];
};

// 取り除く
type MyObj<T> = {
  -readonly [keyof T の P]-?: T[P];
}

TypeScript のネイティブ ツール タイプ Required<T> は、特にオプションの属性を削除します。これは、-? 修飾子を使用して実現されます。

「–?」修飾子がオプションの属性を削除した後は、その属性は「unknown」と等しくなることができず、実際には必須の属性になることに注意してください。ただし、この修飾子は「null」型を削除しません。

さらに、「+?」修飾子は「?」と省略でき、「+readonly」修飾子は「readonly」と省略できます。

タイプ A<T> = {
  +readonly [T のキーの P]+?: T[P];
};

// と同等
タイプ A<T> = {
  readonly [P in keyof T]?: T[P];
};

キー名の再マッピング

文法

TypeScript 4.1 ではキーの再マッピングが導入され、キー名を変更できるようになりました。

タイプ A = {
  foo: 数値;
  バー: 数値;
};

タイプ B = {
  [keyof A の p を `${p}ID` として]: 数値;
};

// と同等
タイプ B = {
  fooID: 数値;
  バーID: 数値;
};

上記の例では、タイプ「B」はタイプ「A」のマッピングですが、属性名はマッピング中に変更され、元の属性名の後に文字列「ID」が追加されます。

ご覧のとおり、キー名の再マッピングの構文は、キー名のマッピングの後に as + new type 句を追加することです。ここでの「新しいタイプ」は通常、元のキー名に対してさまざまな操作を実行できるテンプレート文字列です。

別の例を示します。

インターフェイス 人 {
  名前: 文字列;
  年齢: 番号;
  場所: 文字列;
}

type Getters<T> = {
  [T のキーの P
    as `get${Capitalize<string & P>}`]: () => T[P];
};

type Lazyperson = Getters<人>;
// と同等
type Lazyperson = {
  getName: () => 文字列;
  getAge: () => 数値;
  getLocation: () => 文字列;
}

上記の例では、型 Lazyperson は型 person のマップであり、キー名が変更されます。

キー名を変更するコードはテンプレート文字列get${Capitalize<string & P>}です。以下に各部の説明を示します。

  • get: キー名に追加されるプレフィックス。
  • Capitalize<T>: T の最初の文字を大文字にするために使用されるネイティブ ユーティリティ ジェネリック。
  • string & P​​: クロス型。ここで、P は keyof 演算子によって返されるキー名共用体型 string|number|symbol ですが、Capitalize<T> は型パラメータとして文字列のみを受け入れることができます。 string & P​​ は、P の文字列プロパティ名のみを返します。

属性フィルタリング

キー名の再マッピングにより、特定の属性を除外することもできます。次の例では、文字列属性のみを保持します。

タイプ ユーザー = {
  名前: 文字列、
  年齢:番号
}

type Filter<T> = {
  [T のキーの K
    T[K] は文字列 K を拡張しますか? : 文字列
}

type FilteredUser = Filter<User> // { name: string }

上記の例では、「K in keyof T」をマッピングしてタイプ「T」の各属性を取得した後、「as Type」を使用してキー名を変更します。

そのキー名の再マッピング「as T[K] extends string ? K : Never]」では条件演算子を使用します。属性値 T[K] の型が文字列の場合、属性名は変更されません。そうでない場合、属性名の型は 'never' に変更されます。つまり、属性名は存在しません。これは、条件を満たさない属性をフィルタリングして、属性値が文字列である属性のみを保持することに相当します。

共用体型のマッピング

キー名の再マッピングではキー名の型を変更できるため、元のキー名の型が string|number|symbol である必要はなく、キー名の再マッピングには任意の共用体型を使用できます。

タイプ S = {
  種類: '正方形'、
  x:数値、
  y: 数値、
};

タイプ C = {
  種類: '円'、
  半径: 数値、
};

type MyEvents<Events extends { kind: string }> = {
  [E in Events as E['kind']]: (event: E) => void;
}

タイプ Config = MyEvents<S|C>;
// と同等
タイプ構成 = {
  正方形: (イベント:S) => 無効;
  サークル: (イベント:C) => 無効;
}

上記の例では、元のキー名のマッピングは「E in Events」です。ここで、「Events」は 2 つのオブジェクトで構成される共用体型「S|C」です。したがって、E はオブジェクトであり、キー名の再マッピングを通じて、文字列キー名 E['kind'] が取得されます。

参考リンク


作者: wangdoc

アドレス: https://wangdoc.com/

ライセンス: クリエイティブ・コモンズ 3.0