【TypeScript】 Genericsのextendsでジェネリック関数の引数に制約をつける

JavaScript

extendsを利用することでジェネリクス(Generics)で引数に制約をつけられます。今回は以下の2つのケースについて紹介します。

  • 型の制約をつける場合
  • プロパティの制約をつける場合

型の制約をつける場合

ジェネリック関数(ジェネリックメソッド)の引数をstring型もしくはnumber型のみ許可する場合は以下のように記述します。

const returnValue = <T extends number | string>(value: T): T => value;

// stringは number | string の条件を満たすので問題なし
const str = returnValue<string>("hello");

// numberは number | string の条件を満たすので問題なし
const num = returnValue<number>(10);

// booleanは number | string の条件を満たさないのでコンパイルエラーになる
const truthy = returnValue(true);
// Argument of type 'boolean' is not assignable to parameter of type 'string | number'.ts(2345)

console.log(str);
// hello

console.log(num);
// 10

console.log(truthy);
// コンパイルエラー

ジェネリック関数の引数をShape型のみ許可する場合は以下のように記述します。

type Square = {
  kind: "square";
  size: number;
};

type Rectangle = {
  kind: "rectangle";
  width: number;
  height: number;
};

type Triangle = {
  kind: "triangle";
  width: number;
  height: number;
};

type Shape = Square | Rectangle;

const returnShape = <T extends Shape>(shape: T): T => shape;


const square: Square = { kind: "square", size: 5 };
const triangle: Triangle = { kind: "triangle", width: 5, height: 4 };

// Square型 は Shape型 に含まれているので問題なし
console.log(returnShape<Square>(square).size);

// Triangle型 は Shape型 に含まれているのでコンパイルエラー
console.log(returnShape<Triangle>(triangle).size);
// Type 'Triangle' does not satisfy the constraint 'Shape'.

プロパティの制約をつける場合

例えばsizeプロパティを参照するジェネリック関数を作るとします。その場合、ジェネリクスで定義する型Tにはsizeプロパティが必須になります。
sizeプロパティを持つオブジェクトのみジェネリック関数の引数に許可する場合は以下のように記述します。

type Square = {
  kind: "square";
  size: number;
};

type Rectangle = {
  kind: "rectangle";
  width: number;
  height: number;
};

const getShapeSize = <T extends { size: number }>(shape: T): number =>
  shape.size;

const square: Square = { kind: "square", size: 5 };
const rectangle: Rectangle = { kind: "rectangle", width: 5, height: 4 };

// Square には { size: number } が含まれているので問題ない
console.log(getShapeSize<Square>(square));
// 5

// Rectangle には { size: number } が含まれていないのでコンパイルエラーになる
console.log(getShapeSize<Rectangle>(rectangle));
//  Property 'size' is missing in type 'Rectangle' but required in type '{ size: number; }'.ts(2344)

参考: Array型の引数のみ許可したい場合

Array型のジェネリック関数を作成する場合は、引数の型をジェネリクスのArray(T[])で表現します。extendsの指定は不要です。
具体的には以下のようになります。

const arrayLength = <T>(array: T[]): number => {
  // lengthメソッドを実行するので、引数は型Tではなく配列型T[]を指定する必要がある
  return array.length;
};

const numberArray = [1, 2, 3];
console.log(arrayLength<number>(numberArray));
// 3

const stringArray = ["a", "b", "c", "d"];
console.log(arrayLength<string>(stringArray));
// 4

さいごに

Twitter(@nishina555)やってます。フォローしてもらえるとうれしいです!

参考