はじめに
Typescriptの型をより深く学んでいく際に重要かつ理解に少し時間のかかるようなものをまとめました。
Typescriptの型について初歩的な内容からさらに一歩進んで学びたい人は、後掲する参考記事と共に、この記事を参考にしていただけると嬉しいかぎりです。
ではいこう。
in
まず、Typescriptの型をより深く見始めた時によく目にする'in'。
こいつは何も複雑なことはやっていないので理解はそこまで難しくはないが、異なるニュアンスの使われ方をするので、どう言った意味で使われているのかの判別が必要。
使用例1
inはあるオブジェクトが指定されたプロパティを持っているかをbooleanで返す。
type HumanInfo = {
name: string
age: number
isMarried: boolean
}
console.log("name" in HumanInfo) // true
console.log("address" in HumanInfo) // false
これは型の絞り込みの際にとても役に立つ。
type FlowerInfo = {
name: string
age: number
languageOfFlower: string
}
const validation = (value: HumanInfo| FlowerInfo) => {
if("languageOfFlower" in value) {
value.languageOfFlower.length === 0 ? "花言葉の入力は必須です。" : undefined
}
}
使用例2
こちらの使用例の方が頻出だろう。keyofと併用することでkeyofでreturnさせた直和型のオブジェクトをマッピングできる。
type Flower = {
name: string
age: number
languageOfFlower: string
}
type ReadonlyAll<T> = {
readonly [t in keyof T]: T[t]
}
// type ReadonlyAll<Flower>は、
{
readonly name: string
readonly age: number
readonly languageOfFlower: string
}
Array.map(i => i + 1)のArrayがTでiがtみたいなイメージ。
indexed access operator
Typescriptの型にはtuple型というものが存在する。あくまでも値じゃなくて型。
type AutumnFlower = ['コスモス', 'パンジー', '金木犀']
const a: AutumnFlower = ['コスモス', 'パンジー', '銀木犀'] // コンパイルエラー
このtuple型は、値と同様にして、indexを指定してその番目の要素の型を取得できる
type sample = ['str', 1000, true, () => 1]
sample[0] // string
sample[2] // boolean
さらにtuple[number]とするとそのtupleの要素の型が直和型で求められる。
sample[number] = string | number | boolean | () => number
加えて、lengthメソッドを呼び出せる。要素数の型を取得できる
sample['length'] = 4
Conditional Type
独自の型を定義する時、その型を条件式によって動的に変更することができる。
書き方は三項演算子と似たような感じなので、特に難しい点はない。===とかではなくextendsを使う。
左辺が右辺の型を継承しているか、左辺が右辺に代入可能かを考えれば良い。
type A<T> = T extends number ? T : never
const foo = 10
const bar = "10"
type Foo = A<typeof foo> // number
type Bar = A<typeof bar> // never
Union Distribution
Conditional Typeのなかで、Union型(直和型)の型を扱う時、そのunion型は一つ一つがmapされて、評価される。
Typescript組み込みのExcludeの実装が非常にこの例としてわかりやすい。
type Excludeはあるオブジェクトから指定された要素を除いた型を返すものである。
type Exclude<T, U> = T extends U ? never : T;
type FlowerShop = {
name: string
owner: string
handlingQuantity: number
closeDay: string[]
}
type BasicShopInfo = Exclude<FlowerShop, 'closeDay' | 'handleQuantity'>
// BasicShopInfoは
{
name: string
owner: string
}
//のみ
この時、T extends U ? never : Tのところでは、
{ 'name' extends 'closeDay' | 'handleQuantity' ? never : 'name' },
{ 'owner' extends 'closeDay' | 'handleQuantity' ? never : 'owner' },
{ 'handleQuantity' extends 'closeDay' | 'handleQuantity' ? never : 'handleQuantity' }...
と言った感じでmapされて評価される。この性質がUnion Distribution。
infer
inにつぐ謎word、'infer'。だが、理解はそんなに難しくなく、慣れると結構使えそうなやつ。Conditional Typeと組み合わせて使用する。
簡潔に説明すると、conditional typeの中で出現した型をキャプチャして再利用できる。途中で変数化しておける、みたいなイメージ。
type PickParamType<T> = T extends (x: infer R) => any ? R : never
const foo = 'fooooooo!'
const bar = (x: number) => x + 1
type Foo = PickParamType<typeof foo> // never
type Bar = PickParamType<typeof bar> // number
// barは引数にnumberをとるfunctionであるから、評価式のinfer RのところでRにnumberが入る
交差型
先ほどから直和型やunion typeというワードがちらほら出てきたが、それと対をなすような概念が、この交差型である。
直和型が一つ一つの型を|でつないでいたのに対し、交差型は&でつなぐ。
type Aspect = {
color: string
height: number
}
type Internal = {
languageOfFlower: string
kind: string
}
type Flower = Aspect & Internal // {color: string, height: number, languageOfFlower: string, kind: string}
注意点
&は上書きではないことに注意。どちらかというと制約の追加。
type Foo = {a: string, b: boolean}
type Bar = Foo & {c: number} // {a: string, b: boolean, c: number}
type Zee = Foo & {a: string | () => any} // {a: string, b: boolean}であって{a: string | () => any, b: boolean}ではない
可変長引数
Typescriptでは引数の数を可変にしておくことができる。
type Foo<T extends any[]> = T extends [first: any, ...rest: infer R] ? R : never
type Bar = ['first', 2, true]
type Zee = Foo<Bar> // [2, true]
上記の例では可変とした引数の先頭要素の型を弾いた残りの型を返している。
でもどうやら逆は、同じやり方ではできないらしい。ぴえん🥺
type Foo<T extends any[]> = T extends [...args: infer R, last:any] ? R : never // これはできない。
終わりに
Typescriptの型について、いろいろ調べてチョットダケワカッタ気がした。(本当にチョットだけね。)
値の扱いに比べて抽象度が上がる分少し理解はしづらい面もあるが、じっくり考えればわからないことはない(普段実装しうる範囲内ではね)
参考
https://qiita.com/suin/items/25588b2beba7a3fcce4f
https://qiita.com/Quramy/items/e27a7756170d06bef22a
https://qiita.com/ryo2132/items/ce9e13899e45dcfaff9b
https://qiita.com/SotaSuzuki/items/efe32e07e52dce329653
https://qiita.com/uhyo/items/da21e2b3c10c8a03952f
https://qiita.com/uhyo/items/e2fdef2d3236b9bfe74a