🐈
[Typescript]便利なUtility Types
はじめに
- こんにちは。普段はReact×Typescriptで開発をしているmzdです。
- 最近「開発の中で似たような型を定義することが多いな。」とよく感じていて、もっとスマートに書けたらかっこいいし変更に強い型を作れるんじゃないかなと思ったので、UtilityTypesを一通り調べました。
- その中で便利で良いものが沢山あったので一部紹介していきます。
- また使い方や新たにどういう型を作れるかだけでなく、UtilityTypesがTypescriptにおいて、どのように実装されているかを調べることでTypescript自体の理解も深めることができたので、そこにも触れていきます。
目次
- UtilityTypesとは
- Partial<T>
- Required<T>
- Readonly<T>
- Record<K, T>
- Pick<T, K>
- Exclude<T,U>
- Extract<T,U>
- Omit<T, K>
UtilityTypesとは
公式ドキュメントには下記のように書かれています。
一般的な型変換を容易にするためのいくつかのユーティリティ型
- この言葉の通りUtilityTypesは既に作成された型から「型変換をする」 ≒ 「新しい型を作成する」便利な型です。
- これらはTypeという名からわかる通り、Typescript内で
type Partial<T> =
のように型として定義されています。 - そして、この
<T>
に既に作成された型を渡すことで、新しい型を簡単に作ることができ、私たちは変更に強い型を定義することやコードの冗長化を防ぐことができます。
例として使用する型
- ここから、いくつかのUtilityTypesについて例を参考に見ていきます。
- その時に既に定義された型として下記の
CharacterType
を基本的に使いますが、この型だけでは説明できない場合はその都度、型を定義します。 - ( HUNTER × HUNTERが大好きなので、そのキャラクター名が出てきますが、あまり気にしないでください。)
interface CharacterType {
id: number;
name: string;
age: number;
}
Partial<T>
- Partial<T>は、
T
の全てのプロパティをOptional(任意)にします。
type CharacterPartialType = Partial<CharacterType>
const characterPartial :CharacterPartialType = {
name: "ゴン=フリークス";
}
# CharacterTypeであればid, ageも必要だがCharacterPartialTypeはPartial<T>によってOptionalになったので
characterPartialはid, ageがオブジェクトに無くても型エラーは発生しません。
TypescriptのPartial<T>の定義
type Partial<T> = {
[P in keyof T]?: T[P];
};
-
P in keyof T
-
T
のkeyを一つずつ取り出してP
に再定義しています。 - 今回の例で言うと
P
はid, name, ageの3つになります。
-
-
T[P]
-
T
のP
というkeyの型を意味します。 - 今回の
P
はid, name, ageなので、この3つそれぞれの型(number, string, number)になります。
-
-
?
-
?
をkeyの:
の直前に付与することでOptional(任意)にしています。
-
- このようにしてPartial<T>で受け取った
T
から型を再作成して、keyに対して?
をつけることで、全てのプロパティをOptionalのプロパティにしています。
Required<T>
- Required<T>は、Partial<T>とは反対で、
T
の全てのプロパティをRequired(必須)にする。
# 全てOptionalのCharacterType
interface CharacterType {
id?: number;
name?: string;
age?: number;
}
type CharacterRequiredType = Required<CharacterType>
const characterRequired :CharacterRequiredType = {
name: "キルア=ゾルディック" # idとageが不足
}
# CharacterRequiredTypeはCharacterTypeのプロパティを必須にした型なので
上記のコードだとidとageが不足しているため下記の型エラーが出ます。
# 型 '{ name: string; }' には 型 'Required<CharacterType>' から
次のプロパティがありません: id, age ts(2739)
TypescriptのRequiredの定義
type Required<T> = {
[P in keyof T]-?: T[P];
};
-
P in keyof T
-
T
のkeyを一つずつ取り出してP
に再定義しています。 - 今回の例で言うと
P
はid, name, ageの3つになります。
-
-
T[P]
-
T
のP
というkeyの型を意味します。 - 今回の
P
はid, name, ageなので、この3つそれぞれの型(number, string, number)になります。
-
-
-?
-
?
はOptional(任意)を表すもので、?
をP
のそれぞれから除くことで必須に変更しています。
-
Readonly<T>
- Readonly<T>は、
T
の全てのプロパティをReadonly
にします。 -
Readonly
になったプロパティは再代入不可となり変更できないようになります。
type CharacterReadOnlyType = Readonly<CharacterType>
const CharacterReadOnly: CharacterReadOnlyType = {
id: 403,
name: "クラピカ",
age: 17
}
CharacterReadOnly.id = 99 // エラー
# CharacterReadOnlyTypeは`Readonly`のため、再代入しようとするとエラーになります。
TypescriptのReadonly<T>の定義
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
-
[P in keyof T]
-
T
のkeyを一つずつ取り出してP
に再定義しています。 - 今回の例で言うと
P
はid, name, ageの3つになります。
-
-
T[P]
-
T
のP
というkeyの型を意味します。 - 今回の
P
はid, name, ageなので、この3つそれぞれの型(number, string, number)になります。
-
-
readonly
- 各プロパティの直前に
readonly
を付与することでその型を再代入不可にしています。 - Readonly<T>を使用した場合、全てのプロパティが再代入不可になりますが、ある特定のプロパティのみ再代入不可にしたい場合は、そのプロパティにのみ
readonly
を付与することで実現できます。
- 各プロパティの直前に
Record<K, T>
- Record<K, T>は、
K
をプロパティとしてT
を持つ型を作成します。
type CharacterRecordType = Record<number, CharacterType>
const CharacterRecord: CharacterRecordType = {
99: {
id: 99,
name: "キルア=ゾルディック",
age: 12
},
2: {
id: 403,
name: "レオリオ=パラディナイト",
age: 19
}
}
# K => number, T => CharacterTypeなので、numberをプロパティとしてCharacterTypeを持つ型を作成します。
TypescriptのRecord<K,T>の定義
type Record<K extends keyof any, T> = {
[P in K]: T;
};
-
P in K
-
K
の値を一つずつ取り出してP
に割り当てています。 - 今回の場合は
P
はnumberになります。
-
-
K extends keyof any
-
any
で定義されているプロパティ名のみを許可するという意味になります。 - そのため
K
にはプリミティブ型だけでなく、下記のような定義した型を渡すことも出来ます。
-
# 猫の品種の型
# 下記のHUNTER × HUNTERで書こうと思いましたが、若干理解を妨げそうだと感じたので猫にしました。
type CatVarietyType = "AmericanShorthair" | "Siamese" | "ScottishFold"
type CatRecordType = Record<CatVarietyType, CatType>
const CatRecord: CatRecordType = {
AmericanShorthair: {
id: 1,
name: "アメリカンショートヘア",
age: 1
},
Siamese: {
id: 2,
name: "シャム",
age: 2
},
ScottishFold: {
id: 3,
name: "スコティッシュホールド",
age: 3
}
}
Pick<T,K>
- Pick<T,K>は、
T
の中からK
のプロパティを持つ型を作成します。
type CharacterPickType = Pick<CharacterType, "id" | "name">
const CharacterPick: CharacterPickType = {
id: 16,
name: "トンパ",
}
# CharacterTypeの中からidとnameを取り出して型を作成します。
# `K`でageは指定していないのでCharacterPickTypeにageのプロパティはありません。
TypescriptのPick<T,K>の定義
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
-
K extends keyof T
-
T
で定義されているプロパティ名のみを許可するという意味になります。
-
-
P in K
-
K
の値を一つずつ取り出してP
に割り当てています。
-
-
T[P]
-
T
のP
というkeyの型を意味します。 - 今回の場合
P
はid, nameなので、numberとstringがそれぞれ割り当てられます。
-
Exclude<T,U>
- Exclude<T,U>は
T
のプロパティでU
に代入できないもので型を作成します。
type CharacterType = 1 | "クロロ" | 26 | "男性"
type CharacterExcludeType = Exclude<CharacterType, string>
const CharacterExclude: CharacterExcludeType = 1 || 26
# CharacterTypeの中でstringでないものだけを残して型を作成するので
CharacterExcludeTypeは1と26になります。
TypescriptのExclude<T,U>の定義
type Exclude<T, U> = T extends U ? never : T;
-
T extends U ? never : T
-
T
がU
なら never, それ以外はT
の型を返します。 - CharacterTypeの1 | "クロロ" | 26 | "男性"のそれぞれがstringかどうかを検証して当てはまればnever型を返し当てはまらなければ
T
の型を返します。
-
- 今回の例でstringでないのはnumberである1と12だけなので
CharacterExcludeType = 1 | 26
となります。
Extract<T,U>
- Extract<T,U>はExclude<T,U>とは反対に
T
のプロパティでU
に代入できるもので型を作成します。
type CharacterType = 1 | "クロロ" | 26 | "男性"
type CharacterExtractType = Extract<CharacterType, string>
const CharacterExtract: CharacterExtractType = "クロロ" || "男性"
# CharacterTypeの中でstringのものだけで型を作成するので、
CharacterExtractTypeは"クロロ"と"男性"になります。
TypescriptのExclude<T,U>の定義
type Extract<T, U> = T extends U ? T : never;
-
T extends U ? T : never
- Exclude<T,U>の定義の反対で
T
がU
ならT
の型, それ以外はneverを返します。 - CharacterTypeの1 | "クロロ" | 26 | "男性"のそれぞれがstringかどうかを検証して当てはまれば
T
の型を返し当てはまらなければnever型を返します。
- Exclude<T,U>の定義の反対で
- 今回の例でstringなのは"クロロ"と"男性"だけなのでCharacterExtractType = "クロロ" || "男性"となります。
Omit<T,K>
- Omit<T,K>は、
T
の中からK
のプロパティを除いた型を作成します。 - Pick<T,K>と反対のことができます。
type CharacterOmitType = Omit<CharacterType, "id" | "name">
const CharacterOmit: CharacterOmitType = {
age: 1,
}
# CharacterTypeの中からidとnameを除いた型を作成するので、
age: numberのみが型になります。
TypescriptのOmit<T,K>の定義
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
-
K extends keyof any
-
any
で定義されているプロパティ名のみを許可するという意味です。
-
-
Pick<T, Exclude<keyof T, K>>
- まず
Exclude<keyof T, K>
でT
であるCharacterTypeのkey(id, name, age)の中からK
(id, name)に当てはまらないものを取得するのでageが残ります。 - そうなると
Pick<T, "age">
になり、Pick<T,K>でT
であるCharacterTypeの中からageとその型を取り出して新しい型を作成するので{age: number}が取り出されます。 - その結果、CharacterOmitType = {age: number}となります。
- まず
おわりに
- ここまで読んでいただきありがとうございました。
- この記事を通して、皆様のUtilityTypesの理解の助けになればと思います。
- 今回紹介した以外にもUtilityTypesは、まだありますのでご興味があれば公式ドキュメントをご覧ください。
- 今回は基本的な使い方をメインで触れさせていただきましたが今後、積極的に実務でも使うことでより具体的な使い方や「こういう場面で使うと良い」など知見が溜まった時に記事を書きたいと思います。
Discussion