LoginSignup
2
2

More than 3 years have passed since last update.

【書いて覚えるTypeScript 】ジェネリクス(Generics)編

Last updated at Posted at 2019-10-06

動作環境

今回もPlaygroundでガシガシ書いていきます。
TypeScript Playground

参考

TypeScript Handbook

ジェネリクスとは

汎用的なメソッドやクラスに対して、様々な型を紐付けることができる機能です。

通常TypeScriptでは以下のように関数を定義します。

function returnValue(value: string): string {
  return value;
}

ですが、これだと引数にString型しか渡せません。Number型やArray型など他の型でも使いたいとなったら、同じような関数を何個も定義することになってしまいます。

ここで、ジェネリクスの出番です。先程のreturnValueをジェネリクスを使って書き換えてみます。

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

let result = returnValue<number>(1);
console.log(result); // => 1

result = returnValue<string>('Hello, Generic!!');
console.log(result); // => Hello, Generic!!

関数名の後<T>を追加が追加されています。このTは型引数と言いい、型を引数のように呼び出し時に渡すことができます。

今回の例だと、型引数が引数valueと戻り値のデータ型にもなっているので、
引数として受け取った型Tのvalueを受け取り、戻り値の型もTになるという意味です。

呼び出すときには、returnValue<string>('Hello, Generic!!');といったように、<>でデータ型を囲って指定します。

※ 慣例的に、型引数にはT, U, Rなどがよく使われるようです。

ジェネリクスと関数

もう少し複雑な例をあげようと思います。
以下のような、idプロパティを持つPostクラスとCategoryクラスがあるとします。

class Post {
    id: number;
    title: string;
    constructor(id: number, title: string) {
        this.id = id;
        this.title = title;
    }
}

class Category {
    id: number;
    name: string;
    constructor(id: number, name: string) {
        this.id = id;
        this.name = name;
    }
}

この2つのクラスの配列に対して同一のidのものがあれば、要素を置き換えるようなreplace関数を実装してみます。

replace(array, newItem);

(Reduxのreducerで良く行いそうな処理ですね。)

ひとまず、実装してみる


function replace<T>(data: T[], newItem: T): T[] {
    const index = data.findIndex(item => item.id === newItem.id); // Property 'id' does not exist on type 'T'.
    if (index < 0) {
        console.error(`id: ${newItem.id} not found.`); // Property 'id' does not exist on type 'T'.
        return data;
    }

    return [
      ...data.slice(0, index),
      newItem,
      ...data.slice(index + 1),
    ];
}

完成!!簡単!と言いたいところですが、エラーが出てしまっています。

Property 'id' does not exist on type 'T'. (Tには、idプロパティが無いよー)

そこで、「Tにはidプロパティがある」という制約を付けます。

型引数Tに制約をつける

extendsキーワードを使うことで制約をつけれます。
今回の例では、<T><T extends { id: number }とするれば、「T{ id: number} とい構造を持っている(継承している)」という制約を付けることができます。

その他にも、例えば、<T extends MyClass> とクラス名を書くことで、型引数Tには、「MyClassとその派生クラスだけを受け取る」という制約をつけることができます。

それでは、先程のコードを書き換えましょう。

function replace<T extends { id: number }>(data: T[], newItem: T): T[] {
    const index = data.findIndex(item => item.id === newItem.id);
    if (index < 0) {
        console.error(`id: ${newItem.id} not found.`);
        return data;
    }

    return [
      ...data.slice(0, index),
      newItem,
      ...data.slice(index + 1),
    ];
}

これで完成です。実際に呼び出して確認してみます。

動作確認

const postArray = [
    new Post(1, 'post1'),
    new Post(2, 'post2'),
    new Post(3, 'post3'),
    new Post(4, 'post4')
];

const categoryArray = [
    new Category(1, 'typescript'),
    new Category(2, 'coffeescript'),
    new Category(3, 'es6'),
]

const newPost = new Post(3, 'TypeScriptについて');
const newCategory = new Category(1, 'TypeScript');

let postArrayResult = replace(postArray, newPost);
console.log(postArrayResult);

let categoryArrayResult = replace(categoryArray, newCategory);
console.log(categoryArrayResult);

スクリーンショット 2019-10-06 23.23.17.png

ジェネリクスとクラス

クラス名の後に<T>のような型引数を付与すれば、クラスに対しても使用することができます。

以下は、TypeScript Handbookの例に少し手を加えたものです。

class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
    getZeroValue(): T {
        return this.zeroValue;
    }
}

let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function (x, y) { return x + y; };
console.log(myGenericNumber.getZeroValue()); // => 0


let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function(x, y) { return x + y; };
console.log(stringNumeric.add(stringNumeric.zeroValue, "test")); // => test

クラスに付与された型引数は、そのクラスのプロパティやメソッド定義で使用することができます。

ジェネリクスまとめ

  • 関数名やクラス名の後に<T>といったように型引数を付与する
  • 型引数は関数内やクラス内で使用することができる
  • 型引数に制約を付けたい場合は<T extends ParentClass>のようにextendsを使う
2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2