プログラミング言語

【TypeScript】Union TypesとGenericsの使い方をご紹介 〜型操作応用編〜

【TypeScript】Union TypesとGenericsの使い方をご紹介 〜型操作応用編〜

目次

  1. リテラル型
  2. Union Types
  3. Generics
  4. 最後に

前回に引き続きTypeScriptについて筆者が学習したことをまとめていこうと思います。

今まで型の基本構文から型の種類、intefaceとType Aliasまでを紹介してきました。

前回までの記事をまだご覧でない方は、是非そちらの記事も併せてご覧ください。

【TypeScript】TypeScriptとは?概要やメリット、環境構築からHelloWorld表示
https://www.dailyupblog.com/languages/1680/

前回の記事:【TypeScript】基本構文と基本型、interfaceやType Aliasまでをご紹介
https://www.dailyupblog.com/languages/1717/

今回は、型の和集合を設定できるUnion Typesと型をパラメーターとして扱えるGenericsについて紹介していきます。

どちらも型操作の便利な機能です。

それではいきましょう。

リテラル型

Union Typesを紹介する前に、まず「リテラル型」についてご紹介します。

前回の記事で、string型やnumber型といった基本の型の種類を紹介しましたが、前述の通りそれらの方は「プリミティブ型」といいます。

このプリミティブ型以外にも型には種類が存在して、その一つがこのリテラル型です。

リテラル型とは、ある特定の数値や文字列を一つの型として扱うことができるものです。

例えば下記の例です。

type One = 1;
type Foo = "foo";

let one: One = 1;

let foo: Foo = "foo";

foo = "hoge"; //エラーになる

上記のようにOne型とFoo型という二つの型を宣言しました。

One型は1型の別名であり、Foo型は’foo’型の別名になります。

これらはそれぞれ、数値「1」と文字列「’foo’」しか代入できず、それ以外の値を代入するとエラーになります。

このリテラル型は以降に紹介するUnion Typesと併用して使うと便利です。

Union Types

Union Typesは、型の和集合(いずれかの型、型のOR条件)を定義できるものになります。

例えば、下記の例です。

type Clothes = {
	name: string,
	size: "S" | "M" | "L",
	color: string
}

let item01: Clothes = {
	name: "hoodie",
	size: "M",
	color: "red"
}

この例は、アパレルのECサイトなどを想定して、服のデータの型の値をUnion Typesを使って定義したものになります。

Clothes型に内包されている「size」プロパティは値として「’S’」か「’M’」か「’L’」のどれかしか代入できません。

このように「|」で繋げることで複数の型を和集合で定義できます。

また下記の例です。

type Character = {
	type: "Warrior",
	hp: number,
	ap: number
} | {
	type: "Sorcerer"
	hp: number,
	mp: number
}

let player01: Character = {
	type: "Warrior",
	hp: 100,
	ap: 200
}

const characterStatus = (character: Character): string => {
	if(character.type == "Warrior"){
		return `職業:戦士\nHP:${character.hp}\nAP:${character.ap}`;
	} else if(character.type == "Sorcerer") {
		return `職業:魔術師\nHP:${character.hp}\nMP:${character.mp}`;
	}
}

console.log(characterStatus(player01));

//出力結果
//職業:戦士
//HP:100
//AP:200

上記の例では、RPGゲームの使用キャラクターのステータスを出力するといったものです。

Caracter型は、「type」プロパティの値によって、他のプロパティが異なるように記述しています。

「type」プロパティが「’Warrior’」の時は、その他二つのプロパティは「hp」と「ap」です。

それに対し、「type」プロパティが「’Sorcerer’」の時は、その他二つのプロパティは「hp」と「mp」になります。

変数「player01」にCaracter型を宣言し、「type」プロパティを「’Warrior’」にしました。

なので、もし「ap」プロパティを「mp」にしたら、エラーになります。

そして、関数「characterStatus」は、キャラクターのステータスを出力する関数です。

引数でCharacter型を受け取り、「type」プロパティの値によって、出力する内容を出し分けています。

Generics

Genericsは、型をパラメーターとして扱うことができるものです。

例えば、処理は同じですが、扱う型の種類だけ違う二つの関数があったとします。

const stringArgFunc = (arg: string): string => {
	return arg;
}
const numberArgFunc = (arg: number): number => {
	return arg;
}

この二つの関数はどちらも、受け取った引数をただ返すだけの関数ですが、上のものはstring型の引数を受け取り、string型の戻り値を返して、下のものはnumber型の引数を受け取り、number型の戻り値を返しています。

上記の例は短い処理なので問題ないですが、これが長い処理の関数だった場合、ただ扱う型が違う同一処理の関数を重複して書くのは冗長なコードになってしまいます。

これの解決策で一つ思いつくのは関数の引数と戻り値の型をany型にすることですが、前回の記事でも書きましたが、any型を使うのは危険なので避けたいです。

この問題に関しては、Genericsを使うことで解決できます。

Genericsでは前述の通り型をパラメーターとして扱えるため、上記のような扱う型が違うだけの関数の場合に引数と戻り値の型をパラメーターとしてどんな型でも当てはめることができるようになります。

先ほどの例をGenericsを使って書き換えてみます。

const anyArgFunc = <T>(arg: T): T => {
	return arg;
}

console.log(anyArgFunc<string>("こんにちは")); //こんにちは

console.log(anyArgFunc<boolean>(true)); //true

関数名の直後に角カッコ<>でくくり型パラメーターを宣言します。そして、その後の型指定をすべき箇所には全てこの型パラメーターを指定します。

今回は型パラメーターのは「T」としています。この「T」はTypeのTを表しており、GenericsのパラメーターでTはよく使われるようです。

ただ、任意なのでもっとわかりやすい名前にしても良いです。

上記の例の関数は、何かしらの型「T」の引数を受け取って、何かしらの型「T」で値を返すといった関数になるということです。

その後、この関数を呼び出す際に、関数名直後の角カッコ<>に型名を指定して、引数を記述します。

もう一つ例を見てみましょう。

interface AnyTypeFunc<T> {
	(src: T): T;
}

const doubleCallFunc = <T>(src: T, func: AnyTypeFunc<T>) => {
	return func(func(src))
}

const returnNumber = (num: number): number => {
	return num + num;
}

const returnString = (str: string): string => {
	return str + str;
}

console.log(doubleCallFunc<number>(1,returnNumber)); //4

console.log(doubleCallFunc<string>("あ",returnString)); //ああああ

まず、interfaceでAnyTypeFuncの型名の後ろに型パラメーター<T>を付けています。

そして、何かしらの型「T」を引数で受け取り、何かしらの型「T」の戻り値を返すという内容の関数型を宣言しています。

その後の関数「doubleCallFunc」は第二引数で受け取った関数を二回呼び出すという関数二回呼び出し関数です。

この第二引数の関数にinterfaceのAnyTypeFunc型を指定しています。

このdoubleCallFunc関数もGenericsの型パラメーター<T>を宣言しています。

その後に数値を倍にする関数と、文字列を繋げる関数を用意しました。

先ほどのdoubleCallFunc関数にこれら二つの関数を当てはめて、関数の2回呼び出しを行なっています。

このように、Genericsは関数のみならず、interfaceやType Aliasなどでも扱うことができます。

最後に

今回は、TypeScriptのUnion TypesとGenericsの使い方を紹介しました。

どちらも、型操作において非常に便利な機能ですので、是非ともマスターしたいところです。

TypeScriptは、型に厳格な故にこのような型を扱う上で便利な機能がたくさんあります。

これらを駆使してTypeScriptでコードが書けるようになると、今まで書いていた安全なコードが安全なコードへと変わります。

TypeScriptが上達するように私も常に型を意識してコードを書いていこうと思います。

前回の記事:【TypeScript】基本構文と基本型、interfaceやType Aliasまでをご紹介
https://www.dailyupblog.com/languages/1717/#chapter-6

次の記事:【TypeScript】TypeSriptにおけるnull・undefinedチェックについて
https://www.dailyupblog.com/languages/1761/

関連記事

関連記事