1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

TypeScript勉強会第二回(2022/1/17) - ジェネリクス編 -

Last updated at Posted at 2022-01-14

第一回はこちら

今回の詳しい内容は以下の公式ドキュメントにまとまっているので、英語が読める人はそちらを読んだほうがいいかも

今回はよく使う表現だけかいつまんで説明する

前提知識

ジェネリクスの話をする前に、ジェネリクスとともによく使われる文法をいくつか紹介します

keyof演算子

keyof演算子は、指定したObject型のメンバのキーをString LiteralのUnion Typeとして列挙する演算子

以下のような感じで動作する

.ts
type A = {
   a: string,
   b: number
}

type a = keyof A // => 'a' | 'b'

typeof演算子

typeof 演算子は、JavaScriptのObjectから型を作り出すことができる演算子。
この演算子があることによって、わざわざ型を別定義する必要がなくなるので非常に便利。

動作するプログラムコードの世界(JavaScriptの世界)の情報を型の世界にもっていく事ができるのでよく使われる

以下のように動作する

.ts
const A = {
   a: 'aaa',
   b: 0
}

type a = typeof A // => { a: string, b: number }

Indexed Access Types

Indexed Access Typesは、ある型の指定したメンバの型を返す演算子
例えば、userオブジェクトのageというプロパティの型を取得したいときに使える

.ts
type Person = { 
  age: number
  name: string 
  alive: boolean
};
type Age = Person["age"] // => number

以上を踏まえた上でジェネリクスを見ていきます。

最も単純なジェネリクス例

以下のような引数をただ返すだけの関数を考えます。

.js
function getArg(arg){
 return arg
}

この関数にどのような型をつけるのが適切でしょうか?
もしも以下のように引数に指定できる型が決まっているのであれば、それを指定すればいいです。

.ts
function getArg(arg: number): number{
 return arg
}

しかし、もしかすると引数に指定できる型にstringとnumberの両方を指定したい。というような場合があるかもしれません

.ts
function getArg(arg: number | string): number | string{
 return arg
}

const result = getArg('テスト') // resultはstring | number型になる

この型付けは間違っているわけではありませんが、以下のような問題点があります

  • 引数にstringを指定した場合でも、返り値の型が string | numberになってしまう

この問題は以下のようにanyを取れるようにしても同じです。引数になにを指定しようが返り値の型はanyになってしまいます

.ts
function getArg(arg: any): any{
 return arg
}

const result = getArg('テスト') // resultはany型になる

この関数の実装から明らかなように、この関数は引数にstringを指定すればstringを返すし、numberを返せばnumberを返します。このように引数の型から返り値の型を絞り込みたいときに、ジェネリクスが活躍します。

以下がジェネリクスを使ったコードです

.ts
function getArg<T>(arg: T): T{
 return arg
}
const result1 = getArg('テスト') // resultはstring型になる
const result2 = getArg(0) // resultはnumber型になる

この関数内で利用できる型変数Tというのを定義することができます。この定義は関数名<型変数名>(){}のような形で、関数名と引数の()の間に<>で囲んで定義します

.ts
function getArg<T> // ⇐ここで型変数を定義

ここで定義した型変数Tは関数内で型付けする際に通常の型と同じように使用できます。ここでは引数argの型と返り値の型にともにTをつけました。

型変数Tの実際の型が決定するのは関数が呼び出されたときです。
TypeScriptのコンパイラは引数argに指定された型を見てそれをT型に割当て、返り値の型を推論します。これによって、引数と同じ型を返す関数を作ることができます。

get関数の型付け

以下のような関数 getValueに型をつけることを考えます。

.js

const a = {
 name: 'a',
 age: 10
}

function getValue(key){
  return a[key]
}

この関数は、以下の性質を持っています。

  • 引数keyはaのプロパティのみ指定できる
  • 返り値の値は引数に指定したkeyのvalueになる

この性質を型を使って表すことを考えます。

まず、引数keyは、aのプロパティのみを指定できるようにしたいので、keyof演算子とtypeof演算子を使って以下のように表すことができます。

.ts
function getValue(key: keyof typeof a){
  return a[key]
}

また、返り値はaのvalueの値になるので、以下のように表すことができます。

.ts
function getValue(key: keyof typeof a): (typeof a)[keyof typeof a]{
  return a[key]
}

しかしこのままでは引数に指定したkeyのvalueを返り値の型に指定することができていません。

.ts
const a = {
   name: 'a',
   age: 10
}

function getValue(key: keyof typeof a): (typeof a)[keyof typeof a]{
  return a[key]
}

const aa = getValue('name') // => string | numberと推論される
const bb = getValue('age') // => string | numberと推論される

しかしジェネリックを使えば引数に応じて返り値の型を変えることができます。
まず、以下のように引数の型を型変数Tとし、extends演算子を使ってTの型を縛ります。
ここでの T extends keyof typeof aの意味は、T型は keyof typeof a型と互換性のある型に制限するという意味です。

.ts
function getValue<T extends keyof typeof a>(key: T): (typeof a)[keyof typeof a]{
  return a[key]
}

そしてここで指定した型変数Tを使って返り値の型を書き直します。
こうすることで、型変数Tを経由して引数の型と返り値の型を連動させることができます。

.ts
const a = {
   name: 'aaa',
   age: 0
}

function getValue<T extends keyof typeof a>(key: T): (typeof a)[T]{
  return a[key]
}

const aa = getValue('name') // => stringと推論される
const bb = getValue('age') // => numberと推論される
const cc = getValue('ccc') // => 存在しないkeyなのでエラーになる

練習問題

以下のようなset関数に型付けをしてみましょう

.ts
const a = {
   name: 'aaa',
   age: 0
}

function setValue(key, value){
  a[key] = value
}
答え
.ts
const a = {
   name: 'aaa',
   age: 0
}

function setValue<T extends keyof typeof a>(key: T,value:(typeof a)[T]){
   a[key] = value
}
1
0
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
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?