128
118

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Typescript型Tips集

Last updated at Posted at 2020-10-17

はじめに

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

128
118
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
128
118

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?