TSでちょくちょく使う独自の汎用型
TypeScriptにはOmit
やReturnType
などいくつかの汎用型が元々定義されているけど、こいつらだけだともう一歩足りないことがよくある。。。
組み合わせれば色々なことができるが毎回書くのも面倒くさいのでいくつか汎用化したもののうち、比較的よく使うものを書き連ねます。
オブジェクト型関連
ValueOf
オブジェクト型の値の型をユニオンとして取得する汎用型。
シンプルな汎用型だけど物理名の長いインターフェースやオブジェクトの値の型を取得したいときに結構使う。
マップ型で定義していなくてもT[string]
で取れたらいいのに、と思ったり……
定義
type ValueOf<T extends Record<string, unknown> = T[keyof T]
使用例
const UNITS = {
WEIGHT: 'kg',
HEIGHT: 'cm',
TIME: 'sec',
} as const
type Unit = ValueOf<typeof UNITS>
// = 'kg' | 'cm' | 'sec'
Replace
オブジェクト型に属する特定のプロパティの型を書き換える汎用型。
APIやライブラリから返ってくるプロパティは数字文字列だけど、実際には数値として扱いたいときなどに使ってる。
定義
type Replace<
T extends Record<string, unknown>,
U extends Partial<Record<keyof T, unknown>>
> = Omit<T, keyof U> & U
使用例
type ExactData = {
name: string
age: number
address: [number, number]
}
type ReadableData = Replace<ExactData, { address: string }>
// = {
// name: string
// age: number
// address: string
// }
Merge
2つのオブジェクト型を統合して、同一名のプロパティを持つ場合にはそのプロパティの型を連結(ユニオン)する汎用型。
ほとんど出番はないけど、inputタグのvalueをあるコンポーネントでは文字列、別のコンポーネントでは数値で扱ってるときなどに親のコンポーネントの定義でしょうがなく使っている。
定義
type Merge<
T extends Record<string, unknown>,
U extends Record<string, unknown>
> = Omit<T, keyof U> &
{
[Prop in keyof U]:
| U[Prop]
| (T extends { [P in Prop]: infer V } ? V : never)
}
使用例
type Ab = {
a: number
b: number
}
type Bc = {
b: string
c: string
}
type Abc = Merge<Ab, Bc>
// = {
// a: number
// b: number | string
// c: string
// }
Rename
オブジェクトに含まれるプロパティ名を変更する汎用型。
ライブラリで定義されているメソッドを拡張したメソッドを定義するときとかに使う。
定義
type Rename<T, Key1 extends keyof T, Key2 extends string> = Omit<
T,
Key1
> & { [key in Key2]: T[Key1] }
使用例
type OldType = {
name: string
age: number
}
type NewType = Rename<OldType, 'name', 'firstName'> & {
lastName: string
}
// = {
// firstName: string
// lastName: string
// age: string
// }
Optional
オブジェクト型に属する特定のプロパティのみを任意項目とする汎用型。
Partial
ではすべてのプロパティが任意となってしまうため、一部だけ任意化したいときに使用。
定義
type Optional<
T extends Record<string, unknown>,
U extends keyof T
> = Partial<Pick<T, U>> & Omit<T, U>
使用例
type A = {
a: number
b: string
c: boolean
}
type B = Optional<A, 'b' | 'c'>
// = {
// a: number
// b?: string
// c?: boolean
// }
配列型・タプル型関連
TupleOf
同一の型から構成されるタプルを定義できる汎用型。
一般的なプロジェクトではあまり出番はないけど、簡単なものでは座標、複雑なものではマトリクスを定義するときなどに便利。
定義
type ExtendTuple<
T,
Tuple extends ReadonlyArray<T>,
Count extends number
> = Tuple['length'] extends Count ? Tuple : ExtendTuple<T, [...Tuple, T], Count>
type TupleOf<T, Count extends number> = ExtendTuple<T, [T], Count>
使用例
type Coordinates = TupleOf<number, 2>
// = [number, number]
type Matrix = TupleOf<TupleOf<number, 4>, 3>
// = [
// [number, number, number, number],
// [number, number, number, number],
// [number, number, number, number],
// ]
数値型関連
LessThan / LessEqual
ある値以下、または未満の自然数を含むユニオン型を生成する汎用型。
下記RangedInt
向けに定義したものだったが、単独での出番もたまにある。
定義
type LessEqual<
Max extends number,
Tuple extends ReadonlyArray<number> = [0]
> = Tuple[Max] extends number
? Tuple[number]
: LessEqual<Max, [...Tuple, Tuple['length']]>
type LessThan<
Max extends number,
Tuple extends ReadonlyArray<number> = []
> = Tuple['length'] extends Max
? Tuple[number]
: LessThan<Max, [...Tuple, Tuple['length']]>
使用例
type LessEqualFour = LessEqual<4>
// = 0 | 1 | 2 | 3 | 4
type LessThanFour = LessThan<4>
// = 0 | 1 | 2 | 3
RangedInt
指定した範囲に属する自然数を全て含むユニオン型を生成する汎用型。
ゲーム開発で値の範囲による場合分けをするときとかに使った。
負の数を含む範囲には非対応。
定義
type RangedInt<Min extends number, Max extends number> = Exclude<
LessEqual<Max>,
LessThan<Min>
>
使用例
type FourToEight = RangedInt<4, 8>
// = 4 | 5 | 6 | 7 | 8
文字列型関連
Camelize
lower_snake_case
の文字列をlowerCamelCase
に変換する汎用型。
主に次項のCamelizeObject
で使用。
定義
type Camelize<T extends string> = T extends `${infer U}_${infer V}`
? Camelize<`${U}${Capitalize<V>}`>
: T
使用例
type SnakeCase = 'aa_bb_cc' | 'ddd_eee_fff'
type CamelCase = Camelize<SnakeCase>
// = 'aaBbCc' | 'dddEeeFff'
CamelizeObject
オブジェクトのプロパティ名をlower_snake_case
からlowerCamelCase
に変換する汎用型。
APIレスポンスやライブラリのプロパティ名がlower_snake_case
で定義されていて気持ち悪い時に使っている。
定義
type CamelizeObject<T> = T extends Record<string, unknown>
? {
[
Key in keyof T as Key extends string ? Camelize<Key> : Key
]: T[Key] extends (infer U)[]
? CamelizeObject<U>[]
: CamelizeObject<T[Key]>
}
: T
使用例
type SnakeResponse = {
id: number
is_active: boolean
obj_item: {
id: number
some_key_1: string
some_key_2: boolean
}
arr_items: {
id: number
some_key_1: string
some_key_2: boolean
}[]
}
type CamelResponse = CamelizeObject<SnakeResponse>
// = {
// id: number
// isActive: boolean
// objItem: {
// id: number
// someKey1: string
// someKey2: boolean
// }
// arrItems: {
// id: number
// someKey1: string
// someKey2: boolean
// }[]
// }
Discussion