著者はCOVID-19救済基金を選択し、 Write forDOnationsプログラムの一環として寄付を受け取りました。

序章

Generics は静的型付け言語の基本機能であり、開発者は types をパラメーターとして別の型、 function 、またはその他の構造体に渡すことができます。 開発者がコンポーネントを汎用コンポーネントにすると、そのコンポーネントに、コンポーネントの使用時に渡される入力を受け入れて強制する機能が与えられます。これにより、コードの柔軟性が向上し、コンポーネントが再利用可能になり、重複がなくなります。

TypeScript は、引数を受け入れ、コードで後で使用されるまで型が不確定になる値を返すコンポーネントに型安全性を導入する方法として、ジェネリックスを完全にサポートします。 このチュートリアルでは、TypeScriptジェネリックの実際の例を試し、それらが関数、型、クラス、およびインターフェースでどのように使用されるかを探ります。 また、ジェネリックスを使用して、マップされた型と条件付き型を作成します。これは、コード内のすべての必要な状況に適用できる柔軟性を備えたTypeScriptコンポーネントの作成に役立ちます。

前提条件

このチュートリアルに従うには、次のものが必要です。

  • TypeScriptプログラムを実行して、例に従うことができる環境。 これをローカルマシンに設定するには、次のものが必要です。 TypeScript関連のパッケージを処理する開発環境を実行するためにインストールされたNodeとnpm(またはyarn)の両方。 macOSまたはUbuntu20.04にインストールするには、「Node.jsをインストールしてMacOSにローカル開発環境を作成する方法」または「Ubuntu20.04にNode.jsをインストールする方法」の「NodeSourcePPAを使用してAptでNode.jsをインストールする」の手順に従います。 これは、Windows Subsystem for Linux(WSL)を使用している場合にも機能します。 さらに、TypeScriptコンパイラ(tsc)がマシンにインストールされている必要があります。 これを行うには、TypeScriptの公式Webサイトを参照してください。
  • ローカルマシン上にTypeScript環境を作成したくない場合は、公式の TypeScriptPlaygroundを使用してフォローできます。
  • JavaScript、特に destructuring、rest演算子 imports /exportsなどのES6+構文に関する十分な知識が必要です。 これらのトピックに関する詳細情報が必要な場合は、JavaScriptシリーズのコーディング方法を読むことをお勧めします。
  • このチュートリアルでは、TypeScriptをサポートし、インラインエラーを表示するテキストエディタの側面を参照します。 これはTypeScriptを使用するために必要ではありませんが、TypeScript機能をさらに活用します。 これらの利点を活用するには、 Visual Studio Code のようなテキストエディターを使用できます。このエディターは、TypeScriptをすぐにサポートします。 TypeScriptPlaygroundでこれらの利点を試すこともできます。

このチュートリアルに示されているすべての例は、TypeScriptバージョン4.2.3を使用して作成されています。

ジェネリック構文

ジェネリックスのアプリケーションに入る前に、このチュートリアルでは、最初にTypeScriptジェネリックスの構文を説明し、次にそれらの一般的な目的を説明する例を示します。

ジェネリックスは、山括弧内のTypeScriptコードで、<T>の形式で表示されます。ここで、Tは渡された型を表します。 <T>は、タイプTのジェネリックとして読み取ることができます。 この場合、Tは、構造体のインスタンスが作成されるときに宣言される型のプレースホルダーとして、パラメーターが関数で機能するのと同じように動作します。 したがって、山括弧内に指定されたジェネリック型は、ジェネリック型パラメーターまたは単に型パラメーターとも呼ばれます。 <T, K, A>のように、複数のジェネリック型を1つの定義に含めることもできます。

注:慣例により、プログラマーは通常、ジェネリック型に名前を付けるために1文字を使用します。 これは構文規則ではなく、TypeScriptの他の型と同じようにジェネリックに名前を付けることができますが、この規則は、ジェネリック型が特定の型を必要としないことをコードを読む人にすぐに伝えるのに役立ちます。

ジェネリックスは、関数、型、クラス、およびインターフェースに表示できます。 これらの各構造については、このチュートリアルの後半で説明しますが、ここでは、ジェネリックスの基本的な構文を説明するための例として関数を使用します。

ジェネリックスの有用性を確認するために、キーのobjectarrayの2つのパラメーターを受け取るJavaScript関数があるとします。 この関数は、元のオブジェクトに基づいて新しいオブジェクトを返しますが、必要なキーのみを使用します。

function pickObjectKeys(obj, keys) {
  let result = {}
  for (const key of keys) {
    if (key in obj) {
      result[key] = obj[key]
    }
  }
  return result
}

このスニペットは、pickObjectKeys()関数を示しています。この関数は、keys配列を反復処理し、配列で指定されたキーを使用して新しいオブジェクトを作成します。

関数の使用方法を示す例を次に示します。

const language = {
  name: "TypeScript",
  age: 8,
  extensions: ['ts', 'tsx']
}

const ageAndExtensions = pickObjectKeys(language, ['age', 'extensions'])

これにより、オブジェクトlanguageが宣言され、ageプロパティとextensionsプロパティがpickObjectKeys()関数で分離されます。 ageAndExtensionsの値は次のようになります。

{
  age: 8,
  extensions: ['ts', 'tsx']
}

このコードをTypeScriptに移行してタイプセーフにする場合は、ジェネリックスを使用する必要があります。 次の強調表示された行を追加することで、コードをリファクタリングできます。

function pickObjectKeys<T, K extends keyof T>(obj: T, keys: K[]) {
  let result = {} as Pick<T, K>
  for (const key of keys) {
    if (key in obj) {
      result[key] = obj[key]
    }
  }
  return result
}

const language = {
  name: "TypeScript",
  age: 8,
  extensions: ['ts', 'tsx']
}

const ageAndExtensions = pickObjectKeys(language, ['age', 'extensions'])

<T, K extends keyof T>は、関数の2つのパラメータータイプを宣言します。ここで、Kには、Tのキーの和集合であるタイプが割り当てられます。 次に、obj関数パラメーターは、Tが表す任意のタイプに設定され、keysは、Kが表す任意のタイプの配列に設定されます。 languageオブジェクトの場合のTは、ageを数値として設定し、extensionsを文字列の配列として設定するため、変数ageAndExtensionsこれで、プロパティage: numberおよびextensions: string[]を持つオブジェクトのタイプが割り当てられます。

これにより、pickObjectKeysに指定された引数に基づいて戻り型が適用され、関数が適用する必要のある特定の型を認識する前に、型指定構造を適用できる柔軟性が得られます。 これにより、Visual Studio CodeなどのIDEで関数を使用する際の開発者エクスペリエンスも向上し、提供したオブジェクトに基づいてkeysパラメーターの提案が作成されます。 これは、次のスクリーンショットに示されています。

TypeScript suggestion based on the type of the object

TypeScriptでジェネリックスがどのように作成されるかを理解したら、特定の状況でジェネリックスを使用する方法の調査に進むことができます。 このチュートリアルでは、最初にジェネリックスを関数で使用する方法について説明します。

関数でジェネリックを使用する

関数でジェネリックスを使用するための最も一般的なシナリオの1つは、すべてのユースケースで簡単に入力できないコードがある場合です。 関数をより多くの状況に適用できるようにするために、ジェネリック型を含めることができます。 このステップでは、これを説明するためにidentity関数の例を実行します。 また、ジェネリックに型パラメーターを直接渡すタイミングの非同期の例、およびジェネリック型パラメーターの制約とデフォルト値を作成する方法についても説明します。

ジェネリックパラメータの割り当て

次の関数を見てください。この関数は、最初の引数として渡されたものを返します。

function identity(value) {
  return value;
}

次のコードを追加して、TypeScriptで関数をタイプセーフにすることができます。

function identity<T>(value: T): T {
  return value;
}

関数をジェネリック型パラメーターTを受け入れるジェネリック関数に変換しました。これは、最初の引数の型であり、戻り型を: Tと同じに設定します。

次に、次のコードを追加して関数を試してください。

function identity<T>(value: T): T {
  return value;
}

const result = identity(123);

resultのタイプは123で、これは渡した正確な番号です。 ここでのTypeScriptは、呼び出し元のコード自体からジェネリック型を推測しています。 このように、呼び出し元のコードはタイプパラメータを渡す必要がありません。 明示的にして、ジェネリック型パラメーターを必要な型に設定することもできます。

function identity<T>(value: T): T {
  return value;
}

const result = identity<number>(123);

このコードでは、resultのタイプはnumberです。 <number>コードで型を渡すことにより、identity関数のジェネリック型パラメーターTを型number。 これにより、引数および戻り値としてnumberタイプが適用されます。

タイプパラメータを直接渡す

カスタムタイプを使用する場合は、タイプパラメータを直接渡すことも役立ちます。 たとえば、次のコードを見てください。

type ProgrammingLanguage = {
  name: string;
};

function identity<T>(value: T): T {
  return value;
}

const result = identity<ProgrammingLanguage>({ name: "TypeScript" });

このコードでは、resultidentity関数に直接渡されるため、カスタムタイプProgrammingLanguageになります。 typeパラメーターを明示的に含めなかった場合、resultのタイプは{ name: string }になります。

JavaScriptを使用する場合に一般的な別の例は、ラッパー関数を使用してAPIからデータを取得することです。

async function fetchApi(path: string) {
  const response = await fetch(`https://example.com/api${path}`)
  return response.json();
}

この非同期関数は、引数としてURLパスを取り、 fetch API を使用してURLにリクエストを送信し、JSON応答値を返します。 この場合、fetchApi関数の戻りタイプは、Promise<any>になります。これは、フェッチのresponseに対するjson()呼び出しの戻りタイプです。物体。

リターンタイプとしてanyを使用することはあまり役に立ちません。 anyは任意のJavaScript値を意味し、それを使用すると、TypeScriptの主な利点の1つである静的な型チェックが失われます。 APIが特定の形状のオブジェクトを返すことがわかっている場合は、ジェネリックスを使用してこの関数をタイプセーフにすることができます。

async function fetchApi<ResultType>(path: string): Promise<ResultType> {
  const response = await fetch(`https://example.com/api${path}`);
  return response.json();
}

強調表示されたコードは、関数をResultTypeジェネリック型パラメーターを受け入れるジェネリック関数に変換します。 このジェネリック型は、関数の戻り型Promise<ResultType>で使用されます。

注:関数はasyncであるため、Promiseオブジェクトを返す必要があります。 TypeScript Promise型は、それ自体が、promiseが解決する値の型を受け入れるジェネリック型です。

関数を詳しく見ると、ジェネリックが引数リストやTypeScriptがその値を推測できるその他の場所で使用されていないことがわかります。 これは、関数を呼び出すときに、呼び出し元のコードがこのジェネリックの型を明示的に渡す必要があることを意味します。

ユーザーデータを取得するためのfetchApiジェネリック関数の可能な実装は次のとおりです。

type User = {
  name: string;
}

async function fetchApi<ResultType>(path: string): Promise<ResultType> {
  const response = await fetch(`https://example.com/api${path}`);
  return response.json();
}

const data = await fetchApi<User[]>('/users')

export {}

このコードでは、Userという新しい型を作成し、その型の配列(User[])をResultTypeジェネリックパラメーターの型として使用しています。 data変数のタイプは、anyではなくUser[]になりました。

注: awaitを使用して関数の結果を非同期的に処理しているため、戻り型はPromise<T>Tの型になります。 、この場合は汎用タイプResultTypeです。

デフォルトのタイプパラメータ

汎用のfetchApi関数を作成する場合と同じように、呼び出し元のコードは常にtypeパラメーターを提供する必要があります。 呼び出し元のコードにジェネリック型が含まれていない場合、ResultTypeunknownにバインドされます。 たとえば、次の実装を考えてみましょう。

async function fetchApi<ResultType>(path: string): Promise<ResultType> {
  const response = await fetch(`https://example.com/api${path}`);
  return 
response.json();
}

const data = await fetchApi('/users')

console.log(data.a)

export {}

このコードは、dataの理論上のaプロパティにアクセスしようとします。 ただし、dataのタイプはunknownであるため、このコードはオブジェクトのプロパティにアクセスできません。

ジェネリック関数のすべての呼び出しに特定の型を追加する予定がない場合は、デフォルトの型をジェネリック型パラメーターに追加できます。 これは、次のように、ジェネリック型の直後に= DefaultTypeを追加することで実行できます。

async function fetchApi<ResultType = Record<string, any>>(path: string): Promise<ResultType> {
  const response = await fetch(`https://example.com/api${path}`);
  return response.json();
}

const data = await fetchApi('/users')

console.log(data.a)

export {}

このコードを使用すると、fetchApi関数を呼び出すときに、ResultTypeジェネリックパラメーターに型を渡す必要がなくなります。これは、デフォルトの型がRecord<string, any>であるためです。 。 これは、TypeScriptがdataを、タイプstringのキーとタイプanyの値を持つオブジェクトとして認識し、そのプロパティにアクセスできるようにすることを意味します。

タイプパラメータの制約

状況によっては、ジェネリック型パラメーターは、特定の形状のみをジェネリックに渡すことを許可する必要があります。 ジェネリックにこの追加の特異性レイヤーを作成するために、パラメーターに制約を設定できます。

すべてのプロパティの文字列値を持つオブジェクトのみを格納できるストレージ制約があるとします。 そのために、任意のオブジェクトを受け取り、元のオブジェクトと同じキーを持つが、すべての値が文字列に変換された別のオブジェクトを返す関数を作成できます。 この関数はstringifyObjectKeyValuesと呼ばれます。

この関数は総称関数になります。 このようにして、結果のオブジェクトを元のオブジェクトと同じ形状にすることができます。 関数は次のようになります。

function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) {
  return Object.keys(obj).reduce((acc, key) =>  ({
    ...acc,
    [key]: JSON.stringify(obj[key])
  }), {} as { [K in keyof T]: string })
}

このコードでは、stringifyObjectKeyValuesreduce arrayメソッドを使用して元のキーの配列を反復処理し、値を文字列化して新しい配列に追加します。

呼び出し元のコードが常にオブジェクトを関数に渡すようにするには、次の強調表示されたコードに示すように、ジェネリック型Tに型制約を使用しています。

function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) {
  // ...
}

extends Record<string, any>はジェネリック型制約と呼ばれ、extendsキーワードの後に続く型にジェネリック型を割り当て可能にする必要があることを指定できます。 この場合、Record<string, any>は、タイプstringのキーとタイプanyの値を持つオブジェクトを示します。 タイプパラメータに任意の有効なTypeScriptタイプを拡張させることができます。

reduceを呼び出す場合、レデューサー関数の戻りタイプは、アキュムレータの初期値に基づいています。 {} as { [K in keyof T]: string }コードは、空のオブジェクト{}にキャストされた型を使用して、アキュムレータの初期値の型を{ [K in keyof T]: string }に設定します。 タイプ{ [K in keyof T]: string }は、Tと同じキーを使用して新しいタイプを作成しますが、すべての値がstringタイプに設定されています。 これはマップタイプと呼ばれ、このチュートリアルでは後のセクションで詳しく説明します。

次のコードは、stringifyObjectKeyValues関数の実装を示しています。

function stringifyObjectKeyValues<T extends Record<string, any>>(obj: T) {
  return Object.keys(obj).reduce((acc, key) =>  ({
    ...acc,
    [key]: JSON.stringify(obj[key])
  }), {} as { [K in keyof T]: string })
}

const stringifiedValues = stringifyObjectKeyValues({ a: "1", b: 2, c: true, d: [1, 2, 3]})

変数stringifiedValuesのタイプは次のとおりです。

{
  a: string;
  b: string;
  c: string;
  d: string;
}

これにより、戻り値が関数の目的と一致することが保証されます。

このセクションでは、型パラメーターを直接割り当てることや、パラメーターの形状にデフォルトと制約を設定することなど、関数でジェネリックを使用する複数の方法について説明しました。 次に、ジェネリックスがインターフェイスとクラスをより多くの状況に適用する方法の例をいくつか紹介します。

インターフェイス、クラス、およびタイプでのジェネリックスの使用

TypeScriptでinterfacesおよびclassesを作成する場合、ジェネリック型パラメーターを使用して、結果のオブジェクトの形状を設定すると便利です。 たとえば、クラスは、コンストラクターに渡される内容に応じて、さまざまなタイプのプロパティを持つことができます。 このセクションでは、クラスとインターフェースでジェネリック型パラメーターを宣言するための構文を確認し、HTTPアプリケーションの一般的なユースケースを調べます。

ジェネリックインターフェイスとクラス

ジェネリックインターフェイスを作成するには、インターフェイス名の直後にタイプパラメータリストを追加します。

interface MyInterface<T> {
  field: T
}

これは、Tに渡されたタイプによってタイプが決定されるプロパティfieldを持つインターフェイスを宣言します。

クラスの場合、これはほぼ同じ構文です。

class MyClass<T> {
  field: T
  constructor(field: T) {
    this.field = field
  }
}

ジェネリックインターフェイス/クラスの一般的な使用例の1つは、クライアントコードがインターフェイス/クラスをどのように使用しているかによってタイプが異なるフィールドがある場合です。 APIへのHTTPリクエストを処理するために使用されるHttpApplicationクラスがあり、コンテキスト値がすべてのリクエストハンドラーに渡されるとします。 これを行うそのような方法の1つは次のとおりです。

class HttpApplication<Context> {
  context: Context
	constructor(context: Context) {
    this.context = context;
  }

  // ... implementation

  get(url: string, handler: (context: Context) => Promise<void>): this {
    // ... implementation
    return this;
  }
}

このクラスは、getメソッドのhandler関数の引数の型として渡されるcontextを格納します。 使用中、getハンドラーに渡されるパラメーターの型は、クラスコンストラクターに渡されるものから正しく推測されます。

...
const context = { someValue: true };
const app = new HttpApplication(context);

app.get('/api', async () => {
  console.log(context.someValue)
});

この実装では、TypeScriptはcontext.someValueのタイプをbooleanとして推測します。

ジェネリック型

クラスとインターフェースのジェネリックスの例をいくつか見てきたので、ジェネリックカスタム型の作成に進むことができます。 ジェネリックスを型に適用するための構文は、それらがインターフェースやクラスに適用される方法と似ています。 次のコードを見てください。

type MyIdentityType<T> = T

このジェネリック型は、typeパラメーターとして渡される型を返します。 次のコードでこのタイプを実装したと想像してください。

...
type B = MyIdentityType<number>

この場合、タイプBはタイプnumberになります。

ジェネリック型は、特にマップされた型を使用する場合に、ヘルパー型を作成するために一般的に使用されます。 TypeScriptは、多くのビルド済みヘルパータイプを提供します。 そのような例の1つは、Partialタイプです。これは、タイプTを取り、Tと同じ形状で、すべてのフィールドがオプションに設定された別のタイプを返します。 Partialの実装は次のようになります。

type Partial<T> = {
  [P in keyof T]?: T[P];
};

ここでのタイプPartialは、タイプを受け取り、そのプロパティタイプを反復処理してから、新しいタイプでオプションとして返します。

注: PartialはすでにTypeScriptに組み込まれているため、このコードをTypeScript環境にコンパイルすると、Partialが再宣言され、エラーがスローされます。 ここで引用されているPartialの実装は、説明のみを目的としています。

ジェネリック型がどれほど強力であるかを確認するために、ある店舗からビジネス流通ネットワーク内の他のすべての店舗への送料を格納するオブジェクトリテラルがあるとします。 各ストアは、次のように3文字のコードで識別されます。

{
  ABC: {
    ABC: null,
    DEF: 12,
    GHI: 13,
  },
  DEF: {
    ABC: 12,
    DEF: null,
    GHI: 17,
  },
  GHI: {
    ABC: 13,
    DEF: 17,
    GHI: null,
  },
}

このオブジェクトは、店舗の場所を表すオブジェクトのコレクションです。 各店舗の場所には、他の店舗に発送するためのコストを表すプロパティがあります。 たとえば、ABCからDEFへの発送にかかる費用は12です。 1店舗からそれ自体への送料はnullで、送料は一切かかりません。

他の店舗の場所に一貫した値があり、それ自体に配送される店舗が常にnullであることを確認するために、一般的なヘルパータイプを作成できます。

type IfSameKeyThanParentTOtherwiseOtherType<Keys extends string, T, OtherType> = {
  [K in Keys]: {
    [SameThanK in K]: T;
  } &
    { [OtherThanK in Exclude<Keys, K>]: OtherType };
};

タイプIfSameKeyThanParentTOtherwiseOtherTypeは、3つのジェネリック型を受け取ります。 最初のKeysは、オブジェクトに確実に持たせるために必要なすべてのキーです。 この場合、それはすべての店舗のコードの結合です。 Tは、ネストされたオブジェクトフィールドが親オブジェクトのキーと同じキーを持っている場合のタイプです。この場合は、それ自体に出荷される店舗の場所を表します。 最後に、OtherTypeは、キーが異なる場合のタイプであり、別の店舗に配送する店舗を表します。

次のように使用できます。

...
type Code = 'ABC' | 'DEF' | 'GHI'

const shippingCosts: IfSameKeyThanParentTOtherwiseOtherType<Code, null, number> = {
  ABC: {
    ABC: null,
    DEF: 12,
    GHI: 13,
  },
  DEF: {
    ABC: 12,
    DEF: null,
    GHI: 17,
  },
  GHI: {
    ABC: 13,
    DEF: 17,
    GHI: null,
  },
}

このコードは現在、型の形状を適用しています。 いずれかのキーを無効な値に設定すると、TypeScriptでエラーが発生します。

...
const shippingCosts: IfSameKeyThanParentTOtherwiseOtherType<Code, null, number> = {
  ABC: {
    ABC: 12,
    DEF: 12,
    GHI: 13,
  },
  DEF: {
    ABC: 12,
    DEF: null,
    GHI: 17,
  },
  GHI: {
    ABC: 13,
    DEF: 17,
    GHI: null,
  },
}

ABCとそれ自体の間の送料はnullではなくなったため、TypeScriptは次のエラーをスローします。

Output
Type 'number' is not assignable to type 'null'.(2322)

これで、インターフェイス、クラス、およびカスタムヘルパー型でジェネリックスを使用してみました。 次に、このチュートリアルですでに数回取り上げられているトピックであるジェネリックスを使用したマップ型の作成についてさらに詳しく説明します。

ジェネリックスを使用したマップ型の作成

TypeScriptを使用する場合、別のタイプと同じ形状のタイプを作成する必要がある場合があります。 これは、同じプロパティが必要ですが、プロパティのタイプが異なるものに設定されている必要があることを意味します。 この状況では、マップされた型を使用すると、初期の型の形状を再利用して、アプリケーションで繰り返されるコードを減らすことができます。

TypeScriptでは、この構造はマップ型と呼ばれ、ジェネリックスに依存しています。 このセクションでは、マップされたタイプを作成する方法を説明します。

別のタイプを指定して、すべてのプロパティがboolean値を持つように設定されている新しいタイプを返すタイプを作成するとします。 このタイプは、次のコードで作成できます。

type BooleanFields<T> = {
  [K in keyof T]: boolean;
}

このタイプでは、構文[K in keyof T]を使用して、新しいタイプが持つプロパティを指定しています。 keyof T演算子は、Tで使用可能なすべてのプロパティの名前を持つ共用体を返すために使用されます。 次に、K in構文を使用して、新しい型のプロパティが、keyof Tによって返される共用体型で現在使用可能なすべてのプロパティであることを指定します。

これにより、Kという新しいタイプが作成され、現在のプロパティの名前にバインドされます。 これは、構文T[K]を使用して、元のタイプのこのプロパティのタイプにアクセスするために使用できます。 この場合、プロパティのタイプをbooleanに設定しています。

このBooleanFieldsタイプの使用シナリオの1つは、オプションオブジェクトの作成です。 Userのようなデータベースモデルがあると想像してください。 データベースからこのモデルのレコードを取得するときに、返すフィールドを指定するオブジェクトを渡すこともできます。 このオブジェクトはモデルと同じプロパティを持ちますが、タイプはブール値に設定されています。 フィールドにtrueを渡すと、それを返し、falseを省略します。

次のように、既存のモデルタイプでBooleanFieldsジェネリックを使用して、モデルと同じ形状で、すべてのフィールドがbooleanタイプに設定された新しいタイプを返すことができます。強調表示されたコード:

type BooleanFields<T> = {
  [K in keyof T]: boolean;
};

type User = {
  email: string;
  name: string;
}

type UserFetchOptions = BooleanFields<User>;

この例では、UserFetchOptionsは、次のように作成するのと同じになります。

type UserFetchOptions = {
  email: boolean;
  name: boolean;
}

マップされたタイプを作成するときに、フィールドに修飾子を指定することもできます。 そのような例の1つは、Readonly<T>と呼ばれるTypeScriptで使用可能な既存のジェネリック型です。 Readonly<T>タイプは、渡されたタイプのすべてのプロパティがreadonlyプロパティに設定されている新しいタイプを返します。 このタイプの実装は次のようになります。

type Readonly<T> = {
  readonly [K in keyof T]: T[K]
}

注: ReadonlyはすでにTypeScriptに組み込まれているため、このコードをTypeScript環境にコンパイルすると、Readonlyが再宣言され、エラーがスローされます。 ここで引用されているReadonlyの実装は、説明のみを目的としています。

このコードの[K in keyof T]部分にプレフィックスとして追加されている修飾子readonlyに注意してください。 現在、マップされたタイプで使用できる2つの使用可能な修飾子は、プロパティのプレフィックスとして追加する必要があるreadonly修飾子と、?修飾子です。プロパティの接尾辞。 ?修飾子は、フィールドをオプションとしてマークします。 両方の修飾子は、修飾子を削除するか(-)、追加するか(+)を指定するための特別なプレフィックスを受け取ることができます。 修飾子のみが指定されている場合は、+が想定されます。

マップされた型を使用して、作成済みの型の形状に基づいて新しい型を作成できるようになったので、ジェネリックスの最終的なユースケースである条件付き型付けに進むことができます。

ジェネリックスを使用した条件付き型の作成

このセクションでは、TypeScriptのジェネリックスのもう1つの便利な機能である条件付き型の作成を試してみます。 まず、条件付き入力の基本構造について説明します。 次に、ドット表記に基づいてオブジェクトタイプのネストされたフィールドを省略する条件付きタイプを作成することにより、高度なユースケースを検討します。

条件付きタイピングの基本構造

条件付き型はジェネリック型であり、条件によって結果の型が異なります。 たとえば、次のジェネリック型IsStringType<T>を見てください。

type IsStringType<T> = T extends string ? true : false;

このコードでは、単一の型パラメーターTを受け取るIsStringTypeという新しいジェネリック型を作成しています。 タイプの定義内で、JavaScriptの三項演算子T extends string ? true : falseを使用した条件式のような構文を使用しています。 この条件式は、タイプTがタイプstringを拡張するかどうかをチェックしています。 その場合、結果のタイプは正確なタイプtrueになります。 それ以外の場合は、タイプfalseに設定されます。

注:この条件式は、コンパイル中に評価されます。 TypeScriptは型でのみ機能するため、型宣言内の識別子は常に値ではなく型として読み取るようにしてください。 このコードでは、各ブール値の正確なタイプtrueおよびfalseを使用しています。

この条件付き型を試すには、型パラメーターとしていくつかの型を渡します。

type IsStringType<T> = T extends string ? true : false;

type A = "abc";
type B = {
  name: string;
};

type ResultA = IsStringType<A>;
type ResultB = IsStringType<B>;

このコードでは、ABの2つのタイプを作成しています。 タイプAは、文字列リテラル"abc"のタイプであり、タイプBは、タイプnameと呼ばれるプロパティを持つオブジェクトのタイプです。 string。 次に、IsStringType条件付きタイプで両方のタイプを使用し、結果のタイプをResultAResultBの2つの新しいタイプに格納します。

結果のResultAResultBのタイプを確認すると、ResultAタイプが正確なタイプtrueに設定されており、[X148X ]