ts.svg

【TypeScript】ユーティリティ型

TypeScript

ユーティリティ型とは

ユーティリティ型は、Type Arias で定義した型やインタフェースで定義したオブジェクト構造を基に、別の型を作成するための仕組みです。

Partial<T>

すべてのプロパティを任意プロパティ (?)に設定した型を作成します。

type T1 = {
  x: string
  y: number
}
type T2 = Partial<T1> //{ x?: string, y?: number }

Required<T>

すべてのプロパティを必須プロパティに設定した型を作成します。

type T1 = {
  x?: string
  y?: number
}
type T2 = Required<T1> //{ x: string, y: number }

Readonly<T>

すべてのプロパティを読み取り専用に設定した型を作成します。

type T1 = {
  x: string
  y: number
}
type T2 = Readonly<T1> //{ readonly x: string, readonly y: number }

Record<K, T>

オブジェクトの型定義に使用し、Kにキーの型、Tに値の型を指定します。

type T = Record<'x' | 'y', string> //{ x: string, y: string }

Pick<T, K>

Tの型から、Kで指定したキーを抽出した型を作成します。

type T1 = {
  x: string
  y: number
  z: boolean
}
type T2 = Pick<T1, 'x' | 'y'> //{ x: string, y: number }

Omit<T, K>

Tの型から、Kで指定したキーを除外した型を作成します。

type T1 = {
  x: string
  y: number
  z: boolean
}
type T2 = Omit<T1, 'x' | 'y'> //{ z: boolean }

Exclude<T, U>

ユニオン型において、TからUを除外した型を作成します。

type T1 = 'a' | 'b' | 'c'
type T2 = 'b'
type T3 = Exclude<T1, T2> //'a' | 'c'

Extract<T, U>

ユニオン型において、TからUと一致する型を作成します。

type T1 = 'a' | 'b' | 'c'
type T2 = 'a' | 'c' | 'd'
type T3 = Extract<T1, T2> //'a' | 'c'

NonNullable<T>

ユニオン型において、undefinednullを除外した型を作成します。

type T1 = string | undefined | null
type T2 = NonNullable<T1> // string

Parameters<T>

関数型の引数に対応するタプル型を作成します。

type Func = (x: string, y: number) => void
type T = Parameters<Func> //[string, number]

ReturnType<T>

関数型の戻り値に対応する型を作成します。

type Func = (x: string, y: number) => boolean
type T = ReturnType<Func> //boolean

ConstructorParameters<T>

クラスのコンストラクターの引数に対応するタプル型を作成します。

class C {
  constructor(public x: string, public y: number) {
    this.x = x
    this.y = y
  }
}
type T = ConstructorParameters<typeof C> //[string, number]

InstanceType<T>

クラスのコンストラクターの戻り値に対応する型を作成します。 (結局そのクラスの型になるだけで、使い道がわからない…。)

class C {
  constructor(public x: string, public y: number) {
    this.x = x
    this.y = y
  }
}
type T = InstanceType<typeof C> //C

ThisParameterType<T>

関数型のthisパラメーターに対応する型を作成します。

type Func = (this: number) => string
type T = ThisParameterType<Func> //number

OmitThisParameter<T>

関数型のthisパラメーターを除いた型を作成します。

type Func = (this: number) => string
type T = OmitThisParameter<Func> //() => string

ThisType<T>

まずは以下の例を見てください。

const obj = {
  x: 'hello',
  f1() {
    console.log(this.x)
  },
  f2() {
    this.f1()
  },
}

obj.f1() //hello
obj.f2() //hello

オブジェクト内では、自身のプロパティにthisを使ってアクセスすることができます。

では次の例を見てください。

type T1 = {
  x: string
}
type T2 = {
  f1(): void
  f2(): void
}

const obj: T2 & ThisType<T1> = {
  f1() {
    console.log(this.x)
  }, //これはOK
  f2() {
    this.f1()
  }, //これはNG
}

obj.f1() //undefined

ThisType<T>を使用する場合は、--noImplicitThisが設定されている必要があります。

ThisType<T>は、thisを指定した型に置き換えることができます。 T2 & ThisType<T1>という指定は、「T2thisT1に置き換える」となります。

そのためf1()this.xと参照することはできますが、f2()this.f1()はエラーとなってしまいます。 当然T2xというプロパティを持っていないのでf1()を実行してもundefinedと表示されます。

これだけでは何の意味もないコードです。 実際の使用例として公式のサンプルを見てみます。

参照: https://www.typescriptlang.org/docs/handbook/utility-types.html#thistypetype

type ObjectDescriptor<D, M> = {
  data?: D
  methods?: M & ThisType<D & M> // Type of 'this' in methods is D & M
}

function makeObject<D, M>(desc: ObjectDescriptor<D, M>): D & M {
  let data: object = desc.data || {}
  let methods: object = desc.methods || {}
  return { ...data, ...methods } as D & M
}

let obj = makeObject({
  data: { x: 0, y: 0 },
  methods: {
    moveBy(dx: number, dy: number) {
      this.x += dx // Strongly typed this
      this.y += dy // Strongly typed this
    },
  },
})

obj.x = 10
obj.y = 20
obj.moveBy(5, 5)

少し難しいと思うので、これを少し置き換えてみます。

type D = {
  x: number
  y: number
}
type M = {
  moveBy(dx: number, dy: number): void
}
type ObjectDescriptor = {
  data: D
  methods: M & ThisType<D & M>
}
const od: ObjectDescriptor = {
  data: {
    x: 0,
    y: 0,
  },
  methods: {
    moveBy(dx: number, dy: number) {
      this.x += dx
      this.y += dy
    },
  },
}
const obj: D & M = { ...od.data, ...od.methods }

ポイントは、methodsの型定義です。 M & ThisType<D & M>とすることで、methods内でのthisではDMが参照できるようになります。 そのため、odmoveBy()this.xthis.yが参照できます。

ただし、this.xdata.xとイコールではないことに注意してください。 od.methods.moveBy()と実行しても、dataが変更されることはありません。

最終的にodを展開することで、objは以下のオブジェクトとなります。

const obj = {
  x: 0,
  y: 0,
  moveBy(dx: number, dy: number) {
    this.x += dx
    this.y += dy
  },
}

これであれば、thisが自然な形で使われていることがわかります。

公式サンプルでは、DMを動的に生成できるようにmakeObject()という関数を作成しています。 これは Vue.js のdatamethodsなどで使われているみたいです。

Uppercase<T> / Lowercase<T>

文字列のリテラル型において、それぞれ大文字と小文字に変換します。

type T1 = 'a' | 'B' | 'c'
type T2 = Uppercase<T1> // 'A' | 'B' | 'C'
type T3 = Lowercase<T1> // 'a' | 'b' | 'c'

Capitalize<T> / Uncapitalize<T>

文字列のリテラル型において、先頭の文字をそれぞれ大文字と小文字に変換します。

type T1 = 'abc' | 'Def' | 'ghI'
type T2 = Capitalize<T1> // 'Abc' | 'Def' | 'GhI'
type T3 = Uncapitalize<T1> // 'abc' | 'def' | 'ghI'