【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>
ユニオン型において、undefined
とnull
を除外した型を作成します。
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>
という指定は、「T2
のthis
をT1
に置き換える」となります。
そのためf1()
でthis.x
と参照することはできますが、f2()
のthis.f1()
はエラーとなってしまいます。
当然T2
はx
というプロパティを持っていないので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
ではD
とM
が参照できるようになります。
そのため、od
のmoveBy()
でthis.x
とthis.y
が参照できます。
ただし、this.x
はdata.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
が自然な形で使われていることがわかります。
公式サンプルでは、D
とM
を動的に生成できるようにmakeObject()
という関数を作成しています。
これは Vue.js のdata
やmethods
などで使われているみたいです。
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'