😸

TSでちょくちょく使う独自の汎用型

2022/12/18に公開

TypeScriptにはOmitReturnTypeなどいくつかの汎用型が元々定義されているけど、こいつらだけだともう一歩足りないことがよくある。。。
組み合わせれば色々なことができるが毎回書くのも面倒くさいのでいくつか汎用化したもののうち、比較的よく使うものを書き連ねます。

オブジェクト型関連

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