1.はじめに
型引数というものがあるということは知っていましたが、
イマイチよくわからないまま使っていました。
調べてみると、
「関数の引数と同じように使えるんだ。なんか難しく捉えていた
と感じました。
@uhyo さんの「プロを目指す人のための TypeScript 入門」を元にキャッチアップしました。
間違って解釈している所ありましたら、ご指摘いただけますと幸いです。
2.目次
1. はじめに
2. 目次
3. この記事でわかること
4. 環境
5. 型引数
5.1. 型引数とは?
5.2. 複数の型引数
5.3. 型引数を持つ型を使う
5.4. 型引数に型を入れて使う
5.5. 型引数の制約
5.6. デフォルトの型引数
5.7. 制約とデフォルトを同時に使う
6. おわりに
7. 参考
3.この記事でわかること
型引数の基礎について理解できます。
- 型引数の使いかた
- 型引数に制約を与える使いかた
- 型引数のデフォルト型の使いかた
最終的にこのコードを理解することができます。
type Engineer = {
name: string;
year: number;
frontSkillList?: {
languageList: Array<string>;
frameworkList: Array<string>;
}
backSkillList?: {
languageList: Array<string>;
frameworkList: Array<string>;
}
};
type FrontendEngineer = {
name: string;
year: number;
frontSkillList: {
languageList: Array<string>;
frameworkList: Array<string>;
}
};
type ProjectManager = {
name: string;
year: number;
frontSkillList: {
languageList: Array<string>;
frameworkList: Array<string>;
}
backSkillList: {
languageList: Array<string>;
frameworkList: Array<string>;
}
isPM: boolean;
};
type EngineerTeam<Programer extends Engineer, PM extends Engineer = ProjectManager> = {
teamName: string;
employee: Programer;
director: PM;
};
type FrontTeam = EngineerTeam<FrontendEngineer>;
4.環境
- TypeScript: 4.7.4
- Node.js: 16.15.1
5.型引数
5.1.型引数とは?
型引数とは、型を定義する時に引数(パラメータ)を持てすことができる型です。
引数は宣言の中でだけしか使えません。
型引数を宣言するには、型名のあとに型引数を < >
で囲うことで型引数をもった方を宣言できます。
言葉だけなれべてもイメージがわかないかと思いますので、
実際にどのように使うかを簡単な例で見ていきたいと思います。
type Engineer<T> = {
name: string;
age: number;
skillList: T;
};
-
Engineer<T>
が型引数を持った型(ジェネリック型) -
< >
の中のT
が型引数 -
skillList: T;
の部分が、skillList
プロパティはT
型
という風に使用します。
つまり Engineer<T>
型は、
-
name
プロパティは、string
型 -
age
プロパティは、number
型 -
skillList
プロパティは、T
型
のオブジェクトの型ということになります。
今回は、型引数を T
としましたが、これは任意でつけることができるため、SkillList
型という風にもできます。
type Engineer<SkillList> = {
name: string;
age: number;
skillList: SkillList;
};
5.2.複数の型引数
型引数は、複数設定することができます。
type Engineer<FrontSkillList, BackSkillList> = {
name: string;
age: number;
frontSkillList: FrontSkillList;
backSkillList: BackSkillList;
};
なんか説明がくどくなっちゃいますが、(そんなん説明せんでもわかってるわ!って方は飛ばしてください)
Engineer 型は、
-
name
プロパティは、string
型 -
age
プロパティは、number
型 -
frontSkillList
プロパティは、FrontSkillList
型 -
backSkillList
プロパティは、BackSkillList
型
のオブジェクトの型ということになります。
5.3.型引数を持つ型を使う
型引数の宣言を見てきましたが、
「じゃあこれをどうやって使うんだろう🤔」
となるかと思います。
ここから、型引数を持つ型の使いかたを見て聞きたいと思います。
type Engineer<FrontSkill, BackSkill> = {
name: string;
year: number;
frontSkill: FrontSkill;
backSkill: BackSkill;
};
// Engineer<FrontSkill, BackSkill> 型を使ったオブジェクト
const kakedashiEngineer:Engineer<string, string> = {
name: 'kakedashi',
year: 0,
frontSkill: 'JavaScript',
backSkill: 'PHP'
};
kakedashiEngineer:Engineer<string, string>
というように使います。
これは、型宣言した Engineer<FrontSkill, BackSkill>
に
-
FrontSkill
の型引数にstring
型 -
BackSkill
の型引数にstring
型
を代入するという意味になります。
kakedashiEngineer
は、
-
frontSkill
プロパティはstring
型となり、'JavaScript'
という値 -
backSkill
プロパティはstring
型となり、'PHP'
という値
が入っているオブジェクトとなります。
つまり、
type Engineer = {
name: string;
year: number;
frontSkill: string;
backSkill: string;
};
という型と同じ意味になります。
このように、
型宣言する際には具体的な型を宣言せずに、使用する際に型を定義します。
使いたい用途に合わせて型を宣言できます。
型引数を持った型を使う上で注意があります。
型引数を持った型を使用する場合は、型引数を用いなければエラーとなります。
どういうことかといいますと、
type Engineer<FrontSkill, BackSkill> = {
name: string;
year: number;
frontSkill: FrontSkill;
backSkill: BackSkill;
};
// Engineer<FrontSkill, BackSkill> 型を使ったオブジェクト
const kakedashiEngineer:Engineer = {
name: 'kakedashi',
year: 0,
frontSkill: 'JavaScript',
backSkill: 'PHP',
};
ということです。
これをコンパイルすると
Generic type 'Engineer' requires 2 type argument(s).
ジェネリック型の 'Engineer' は2つの型引数を必要とします。
と注意されてしまいます。
(デフォルト値を設定していれば、使用時に型引数を省略することができます。 → 参照:デフォルトの型引数)
5.4.型引数に型を入れて使う
使用する際に、型引数に型を入れて使うこともできます。
type Engineer<FrontSkillList, BackSkillList> = {
name: string;
year: number;
frontSkillList: FrontSkillList;
backSkillList: BackSkillList;
};
type EngineerSkillList = {
languageList: Array<string>;
frameworkList: Array<string>;
};
const kakedashiEngineer:Engineer<EngineerSkillList, EngineerSkillList> = {
name: 'kakedashi',
year: 0,
frontSkillList: {
languagList: ['JavaScript'],
frameworkList: ['React'],
},
backSkillList: {
languageList: ['PHP'],
frameworkList: ['Laravel'],
},
};
FrontSkillList
と BackSkillList
の方引数に EngineerSkillList
型を代入しています。
frontSkillList
, backSkillList
プロパティはそれぞれ EngineerSkillList
型ということになります。
つまり、
type Engineer = {
name: string;
year: number;
frontSkillList: EngineerSkillList;
backSkillList: EngineerSkillList;
};
という型と同じ意味になります。
更にこの型を分解してみると、
type Engineer = {
name: string;
year: number;
frontSkillList: {
languageList: Array<string>;
frameworkList: Array<string>;
},
backSkillList: {
languageList: Array<string>;
frameworkList: Array<string>;
},
};
という型になります。
5.5.型引数の制約
次に制約を与えた型引数を見ていきます。
型引数に ある型の部分型でなければいけない という制約です。
部分型に関しては、以前記事を書きましたのでご参考にどうぞです。
(ご覧いただいた際についでに いいね してくださると嬉しいです😆 w)
では、どのように制約を与えるのかといいますと、
型引数の宣言の後ろに extends 型
と宣言します。
type Engineer<FrontSkillList extends LanguageSkillList, FrontSkillList extends LanguageSkillList> = {
name: string;
year: number;
frontSkillList: FrontSkillList;
backSkillList: BackSkillList;
};
このように使います。
では、具体的なコードを見てみましょう。
// 型引数に制約を与えた型を宣言
type Engineer<FrontSkillList extends LanguageSkillList, FrontSkillList extends LanguageSkillList> = {
name: string;
year: number;
frontSkillList: FrontSkillList;
backSkillList: BackSkillList;
};
type LanguageSkillList = {
languageList: Array<string>;
}
// LanguageSkillList 型の部分型
type LanguageAndFrameworkSkillList = {
languageList: Array<string>;
frameworkList: Array<string>;
}
// 型引数に制約を与えた型を別名 (Kakedashi) で宣言する
type Kakedashi = Engineer<LanguageSkillList, LanguageAndFrameworkSkillList>;
const kakedashiEngineer:Kakedashi = {
name: 'kakedashi',
year: 0,
frontSkillList: {
languageList: ['JavaScript'],
},
backSkillList: {
languageList: ['PHP'],
frameworkList: ['Laravel'],
},
};
FrontSkillList extends LanguageSkillList
と BackSkillList extends LanguageSkillList
が制約を与えた型引数となります。
型引数 FrontSkillList
、 BackSkillList
に代入する型はLanguageSkillList
の部分型でなければいけません。
つまり、
LanguageSkillList
型の性質を引き継いでいる引数を FrontSkillList
や BackSkillList
に代入しなければいけません
ということです。
下記の内容から、LanguageAndFrameworkSkillList
型は LanguageSkillList
の部分型の関係ということがわかります。
(なぜ?と思う方はこちらの記事をご参考にしてください)
type LanguageSkillList = {
languageList: Array<string>;
}
type LanguageAndFrameworkSkillList = {
languageList: Array<string>;
frameworkList: Array<string>;
}
次に、下記の内容から、
- 型引数の
FrontSkillList
にLanguageSkillList
を代入 - 型引数の
BackSkillList
にLanguageSkillList
の部分型のLanguageAndFrameworkSkillList
を代入
ということがわるかと思います。
type Kakedashi = Engineer<LanguageSkillList, LanguageAndFrameworkSkillList>;
これが制約となります。
したがって、制約にない型引数を代入するとエラーとなります。
type Engineer<FrontSkillList extends LanguageSkillList, BackSkillList extends LanguageSkillList> = {
name: string;
year: number;
frontSkillList: FrontSkillList;
backSkillList: BackSkillList;
};
type LanguageSkillList = {
languageList: Array<string>;
}
type Manager = Engineer<string, number>;
Type 'string' does not satisfy the constraint 'LanguageSkillList'.
('string' 型は制約 'LanguageSkillList' を満たしません。)
制約を与えることで型をより安全に使えますね。
5.6.デフォルトの型引数
型引数を持つ方を使う で、
型引数を持った型を使用する場合は、型引数を用いなければエラーとなります。
と記述しましたが、使用する際に型引数を省略することができます。
それが、デフォルトの型引数です。
では、型引数のデフォルトの型について見ていきます。
これは、型引数を省略してもデフォルトの型を使ってくれるものになります。
型引数の後ろに = 型
と宣言して使います。
この 型
の部分がデフォルト値となります。
type EngineerTeam<Programer = Engineer, PM = Engineer> = {
teamName: string;
director: PM;
employee: Programer;
}
このように使います。
では、具体的に見ていきましょう。
type Engineer = {
name: string;
year: number;
frontSkillList?: {
languageList: Array<string>;
frameworkList: Array<string>;
}
backSkillList?: {
languageList: Array<string>;
frameworkList: Array<string>;
}
};
// 型引数にデフォルトの型を宣言
type EngineerTeam<Programer = Engineer, PM = Engineer> = {
teamName: string;
employee: Programer;
director: PM;
};
// EngineerTeam<Engineer, Enginerr>と同じ
type FrontTeam = EngineerTeam;
const frontTeam: FrontTeam = {
teamName: 'ふろんとチーム',
employee: {
name: 'かけだし',
year: 1,
frontSkillList: {
languageList: ['JavaScript'],
frameworkList: ['React'],
},
},
director: {
name: 'ぶちょう',
year: 20,
frontSkillList: {
languageList: ['JavaScript', 'TypeScript'],
frameworkList: ['Next.js', 'Nuxt.js'],
},
backSkillList: {
languageList: ['PHP', 'Ruby'],
frameworkList: ['Laravel', 'Ruby on Rails'],
}
}
};
ではまずは、デフォルトの型を宣言している部分から見てみます。
// 型引数にデフォルトの型を宣言
type EngineerTeam<Programer = Engineer, PM = Engineer> = {
teamName: string;
employee: Programer;
director: PM;
};
これは、
- 型引数の
Programer
に何も代入されなかったらEngineer
型を使う - 型引数の
PM
に何も代入されなかったらEngineer
型を使う
という意味になります。
では、デフォルト型を使ってみましょう。
type FrontTeam = EngineerTeam;
FrontTeam
型は EngineerTeam
型を使いますが、引数がありません。
なので、これは、引数にデフォルト型が代入されることになります。
type FrontTeam = EngineerTeam<Engineer, Enginerr>;
と同じことになります。
片方の型引数にのみ型を代入することもできます。
type FrontTeam = EngineerTeam<string>;
これは、
type FrontTeam = engineerTeam<string, Engineert>;
と同じです。
ただ、今のところは、右にだけ指定することはできませんのでデフォルト型を設定するときは左に型引数を設ける必要があります。
5.7.制約とデフォルトを同時に使う
制約とデフォルトを同時に使うこともできます。
このように使います。
少しややこしそうに見えますが、今まで見てきたことをまとめただけになります。
type Engineer = {
name: string;
year: number;
frontSkillList?: {
languageList: Array<string>;
frameworkList: Array<string>;
}
backSkillList?: {
languageList: Array<string>;
frameworkList: Array<string>;
}
};
type FrontendEngineer = {
name: string;
year: number;
frontSkillList: {
languageList: Array<string>;
frameworkList: Array<string>;
}
};
type ProjectManager = {
name: string;
year: number;
frontSkillList: {
languageList: Array<string>;
frameworkList: Array<string>;
}
backSkillList: {
languageList: Array<string>;
frameworkList: Array<string>;
}
isPM: boolean;
};
// 型引数に制約とデフォルト型を宣言
type EngineerTeam<Programer extends Engineer, PM extends Engineer = ProjectManager> = {
teamName: string;
employee: Programer;
director: PM;
};
// EngineerTeam<FrontendEngineer, ProjectManager>と同じ
type FrontTeam = EngineerTeam<FrontendEngineer>;
const frontTeam: FrontTeam = {
teamName: 'ふろんとチーム',
employee: {
name: 'かけだし',
year: 1,
frontSkillList: {
languageList: ['JavaScript'],
frameworkList: ['React'],
},
},
director: {
name: 'ぶちょう',
year: 20,
frontSkillList: {
languageList: ['JavaScript', 'TypeScript'],
frameworkList: ['Next.js', 'Nuxt.js'],
},
backSkillList: {
languageList: ['PHP', 'Ruby'],
frameworkList: ['Laravel', 'Ruby on Rails'],
},
isPM: true,
}
};
では、型宣言しているところから見てみましょう。
// 型引数に制約とデフォルト型を宣言
type EngineerTeam<Programer extends Engineer, PM extends Engineer = ProjectManager> = {
teamName: string;
employee: Programer;
director: PM;
};
この部分ですね。
- 型引数
Programer
-
Engineer
型の部分型(制約)
-
- 型引数
PM
-
Engineer
型の部分型(制約) - デフォルト型は
ProjectManager
型
-
と意味になります。
ややこしそうに見えますが、単純ですね。
この型を使っているのが、
type FrontTeam = EngineerTeam<FrontendEngineer>;
になります。
これは、
type FrontTeam = EngineerTeam<FrontendEngineer, ProjectManager>;
と同じことです。
6.おわりに
型引数の使いかたの理解が深まりました。
今までわけも分からず使っていたフシがあったので、
これからは内容を理解して上で使えそうです。
というか、型引数を使わないと不便だなと感じるようになりましたw
併せて他の記事も読んでいただけると嬉しいです🙇♂️
最後まで読んでいただきありがとうございました。