TypeScript Deep Dive 日本語版
  • TypeScript Deep Dive 日本語版
  • TypeScript入門 & 環境構築
    • なぜTypeScriptを使うのか?
  • JavaScript
    • 等価演算子の同一性
    • リファレンス
    • nullとundefined
    • this
    • クロージャ
    • Number型
    • Truthy
  • モダンなJavaScriptの機能
    • クラス
      • Classes Emit
    • アロー関数
    • 残余引数(Restパラメータ)
    • let
    • const
    • 分割代入
    • スプレッド演算子
    • for...of
    • Iterator
    • テンプレートリテラル
    • Promise
    • ジェネレータ
    • async await
  • プロジェクトの環境設定
    • コンパイルコンテキスト
      • tsconfig.json
      • コンパイル対象ファイルの設定
    • 宣言空間
    • ファイルモジュール
      • ファイルモジュールの詳細
      • global.d.ts
    • 名前空間
    • 動的インポート
  • Node.js & TypeScriptのプロジェクト作成
  • React & TypeScriptのプロジェクト作成
  • TypeScriptの型システム
    • JavaScriptからの移行ガイド
    • @types パッケージ (DefinitelyTyped)
    • アンビエント宣言(declare)
      • 型定義ファイル
      • グローバル変数の宣言
    • インターフェース
    • Enum
    • lib.d.ts
    • 関数の型
    • 呼び出し可能オブジェクト
    • Type Assertion(型アサーション)
    • Freshness
    • 型ガード
    • リテラル型
    • Readonly
    • ジェネリック型
    • 型推論
    • 型の互換性
    • never
    • 判別可能なUnion型
    • Index signature(インデックス型)
    • 型の移動
    • 例外のハンドリング
    • ミックスイン
  • JSX
    • React
    • React以外のJSX
  • オプション
    • noImplicitAny
    • strictNullChecks
  • TypeScriptのエラー
    • エラーの理解
    • 一般的なエラー
  • NPM
  • テスト
    • Jest
    • Cypress
  • ツール
    • Prettier
    • Husky
    • Changelog
  • その他のヒント
    • String Based Enums
    • Nominal Typing
    • Stateful Functions
    • Bind is Bad
    • Currying
    • Type Instantiation
    • Lazy Object Literal Initialization
    • Classes are Useful
    • Avoid Export Default
    • Limit Property Setters
    • outFile caution
    • JQuery tips
    • static constructors
    • singleton pattern
    • Function parameters
    • Build Toggles
    • Barrel
    • Create Arrays
    • Typesafe Event Emitter
  • スタイルガイド(コーディング規約)
  • TypeScriptコンパイラの内側
    • Program
    • AST
      • TIP: Visit Children
      • TIP: SyntaxKind enum
      • Trivia
    • Scanner
    • Parser
      • Parser Functions
    • Binder
      • Binder Functions
      • Binder Declarations
      • Binder Container
      • Binder SymbolTable
      • Binder Error Reporting
    • Checker
      • Checker Diagnostics
      • Checker Error Reporting
    • Emitter
      • Emitter Functions
      • Emitter SourceMaps
    • Contributing
GitBook提供
このページ内
  • ジェネリクス(Generics)
  • ジェネリクスのモチベーションとサンプル
  • デザインパターン:便利なジェネリック

役に立ちましたか?

  1. TypeScriptの型システム

ジェネリック型

ジェネリクス(Generics)

ジェネリクスの役立つ主な理由は、メンバ間で意味のある型制約を提供することです。メンバには以下のものがあります:

  • クラスのインスタンスメンバ

  • クラスメソッド

  • 関数の引数

  • 関数の戻り値

ジェネリクスのモチベーションとサンプル

単純なQueue(先入れ先出し)データ構造の実装を考えてみましょう。TypeScript/JavaScriptの単純なものは以下のようになります:

class Queue {
  private data = [];
  push(item) { this.data.push(item); }
  pop() { return this.data.shift(); }
}

この実装での1つの問題は、キューに何でも追加できることです。また、キューから要素を取り出すと、何が出てくるかわかりません。これを以下に示します。ここでは誰かがstringをキューにプッシュしていますが、実際にはnumbersだけがプッシュされることを想定しています。

class Queue {
  private data = [];
  push(item) { this.data.push(item); }
  pop() { return this.data.shift(); }
}

const queue = new Queue();
queue.push(0);
queue.push("1"); // Oops a mistake

// a developer walks into a bar
console.log(queue.pop().toPrecision(1));
console.log(queue.pop().toPrecision(1)); // RUNTIME ERROR

1つの解決策(実際にはジェネリクスをサポートしていない言語での唯一の解決策)は、これらの制約のために特別なクラスを作成することです。例えば素早くダーティに数値型のキューを作ります:

class QueueNumber extends Queue {
  push(item: number) { super.push(item); }
  pop(): number { return this.data.shift(); }
}

const queue = new QueueNumber();
queue.push(0);
queue.push("1"); // ERROR : cannot push a string. Only numbers allowed

// ^ if that error is fixed the rest would be fine too

もちろん、これはすぐに苦痛になる可能性があります。文字列キューが必要な場合は、そのすべての作業をもう一度行う必要があります。あなたが本当に必要とすることは、型が何であれ、プッシュされているものの型とポップされたものの型は同じでなければならないということです。これは、ジェネリクスパラメータ(この場合はクラスレベル)で簡単に行えます:

/** A class definition with a generic parameter */
class Queue<T> {
  private data = [];
  push(item: T) { this.data.push(item); }
  pop(): T | undefined { return this.data.shift(); }
}

/** Again sample usage */
const queue = new Queue<number>();
queue.push(0);
queue.push("1"); // ERROR : cannot push a string. Only numbers allowed

// ^ if that error is fixed the rest would be fine too

すでに見たもう1つの例は、_reverse_関数の例です。ここでは、関数に渡されるものと関数が返すものの間の制約があります。

function reverse<T>(items: T[]): T[] {
    var toreturn = [];
    for (let i = items.length - 1; i >= 0; i--) {
        toreturn.push(items[i]);
    }
    return toreturn;
}

var sample = [1, 2, 3];
var reversed = reverse(sample);
console.log(reversed); // 3,2,1

// Safety!
reversed[0] = '1';     // Error!
reversed = ['1', '2']; // Error!

reversed[0] = 1;       // Okay
reversed = [1, 2];     // Okay

このセクションでは、クラスレベルと関数レベルで定義されているジェネリクスの例を見てきました。多少付け加えたいことは、メンバ関数のためだけにジェネリクスを作成できるということです。おもちゃの例として、reverse関数をUtilityクラスに移したところで、次のことを考えてみましょう:

class Utility {
  reverse<T>(items: T[]): T[] {
      var toreturn = [];
      for (let i = items.length - 1; i >= 0; i--) {
          toreturn.push(items[i]);
      }
      return toreturn;
  }
}

ヒント:必要に応じてジェネリクスパラメータを呼び出すことができます。単純なジェネリックを使うときは T、U、Vを使うのが普通です。複数のジェネリクス引数がある場合は、意味のある名前を使用してください。例えばTKeyとTValueです(一般にTを接頭辞として使用する規約は、他の言語(例えばC++)では_テンプレート_と呼ばれることもあります)。

デザインパターン:便利なジェネリック

次の関数を考えてみましょう。

declare function parse<T>(name: string): T;

この場合、タイプTは1つの場所でのみ使用されていることがわかります。したがって、メンバ間に制約はありません。これは、型安全性における型アサーションに相当します。

declare function parse(name: string): any;

const something = parse('something') as TypeOfSomething;

1回だけ使用されるジェネリクスは、型安全性に関してはアサーションよりも劣っています。それは、既に述べたように、あなたのAPIに利便性を提供するものです。

より明らかな例は、jsonレスポンスをロードする関数です。それは、あなたが渡した任意の型のPromiseを返します:

const getJSON = <T>(config: {
    url: string,
    headers?: { [key: string]: string },
  }): Promise<T> => {
    const fetchConfig = ({
      method: 'GET',
      'Accept': 'application/json',
      'Content-Type': 'application/json',
      ...(config.headers || {})
    });
    return fetch(config.url, fetchConfig)
      .then<T>(response => response.json());
  }

あなたは依然としてアノテーションを明示しなければならないことに注意してください。しかし、getJSON<T>のシグネチャ(config) => Promise<T>は、キータイプを減らすことができます(loadUsersの戻り値の型は、TypeScriptが推論可能なのでアノテーションする必要はありません):

type LoadUsersResponse = {
  users: {
    name: string;
    email: string;
  }[];  // array of user objects
}
function loadUsers() {
  return getJSON<LoadUsersResponse>({ url: 'https://example.com/users' });
}

戻り値としてのPromise<T>は、Promise<any>よりも断然優れています。

次の例では、ジェネリクスが引数のみに使用されています。

declare function send<T>(arg: T): void;

このようにすると、次のように、引数にマッチさせたい型をアノテートするのにジェネリクスTを利用できます。

send<Something>({
  x:123,
  // Also you get autocomplete  
}); // Will TSError if `x:123` does not match the structure expected for Something
前へReadonly次へ型推論

最終更新 2 年前

役に立ちましたか?