JavaScript
TypeScript

TypeScript の型の書き方いろいろ

TypeScript (以下 ts) には型の書き方がいろいろあります.

しかしながら型の書き方が分からないときにはどうやってググったらいいのかわからないので困ることも多いです.

もちろん公式ドキュメントを全部読めばこの記事にある内容は必要ないのですが、残念ながら公式ドキュメントは英語なので読まれないことが多いです.

この記事ではよく使う書き方をズラズラ書いていきますので困ったときのリファレンスとして使われることを想定して書いておきます.

Object の型を作る

js の時代は Object の取り扱いは変な型を入れないようにするために神経を使いました.

const person = {
    name: "田中",
    age: 100
}

ts では以下のように型を定義しておけば.

interface Person {
  name: string
  age: number
}

こんな感じに書くことができてとても安全です. 値の型が間違っているとコンパイルエラーになります.

const person: Person = {
  name: "田中",
  age: 100
}
// or
const p = {
  name: "田中",
  age: 100
} as Person

残念ながら以下のように書くとコンパイルエラーにならないので const person: Person 形式で書くのがおすすめです.

const p = {
} as Person

型のエイリアスを作りたい

type キーワードを使って宣言します. 以下の例では string のエイリアスとして UserID という型を作りました. この場合、 UserID は string 型と同じ型を持ちます.

type UserID = string

この書き方に何の意味があるかというと A の書き方より B の書き方のほうが自明で分かりやすいからです.

// A
const findUserById = (id: string) => { /* ... */}

// B
const findUserById = (id: UserID) => { /* ... */}

文字列の enum っぽいのを作りたい

これも type キーワードを使います. 例えばこんな感じです.

type Fruit = "apple" | "banana"
// これを使ってこんな関数を定義して
const eat = (fruit: Fruit) => console.log(`Eat ${fruit}`)

// こんな風に呼ぶことができます.
eat("apple")


// Fruit に定義していない文字列を渡すとエラーになります.
eat("gorilla")

通常、関数の引数に文字列を直接書くのは typo の危険性があってお勧めできないのですが type キーワードを使って予め定義しておけば間違った文字列を書いたときにコンパイルエラーになるので安心です.

ちなみに数値も扱えます. こうすればマジックナンバーっぽいけど安全な書き方ができます.

type Num = 1 | 2 | 3

型を混ぜることもできます.

type Num = 1 | 2 | "san"

更に柔軟にこんな書き方もできます. js の頃はこんな風な型を受け取る関数がありましたね.(((;꒪ꈊ꒪;)))

でも ts なら安全です.

type Num = 1 | 2 | string

Object のある key の値の型を取り出したい

例えば以下のような乗り物を示す型があったとして、ある関数で kind だけ受け取りたい場面があるとします.

interface Vehicle {
    name: string
    kind: "car" | "bike" | "train"
}

そんな場合にこんな風に書くのは NG です. string を受け取ると typo の危険性があります.

const doSomethingForKind =  (kind: string) => {}

しかしながら 

type VehicleKind = "car" | "bike" | "train"

という型を宣言するのは僕が異常なまでなめんどくさがり屋なので書きたくないです.

前置きが長くなりましたが、そんなときには以下のように書けます.

const doSomethingForKind =  (kind: Vehicle["kind"]) => {}

楽ちんですね. また型だけ取り出したい場合は以下のように書くこともできます.

type VehicleKind = Vehicle["kind"]

VehicleKind を宣言してから Vehicle を宣言するか、あるいはその逆にするかはそのアプリケーションの考え方によるのですがこんな書き方もできますよという感じです.

型を足し算する

React をやってると複数の sub component をまとめた親コンポーネントを作る状況がよくあります.

そんな場合には sub component の Props が増えたり減ったりすると親コンポーネントの Props も編集する必要があって面倒です. そんなときに使えるテクニックです.

interface SubComponent_A_Props {
    name: string
}

interface SubComponent_B_Props {
    age: number
}

// 超めんどい
interface ParentComponentProps {
    name: string
    age: number

}

この ParentComponentProps は以下のように書けます.

type ParentComponentProps = SubComponent_A_Props & SubComponent_B_Props 

楽ちんですね.

interfacetype

ts では型を宣言するのに interface と type の2種類の方法があります.

選び方としてはまず interface で書けるか検討します.
書けない場合は type を使います.

理由としては interface は継承したりできて拡張性が高いのですが、 type の方はできることが限られていて拡張性が低いからです.


ここから先は Mapped Types というもう少し複雑な Topic になります.

これが使えるとどんどん ts が楽しくなってくるのでぜひ覚えてください. 最初の頃は難しく感じますが慣れてくると恐ろしく柔軟に型を定義しつつ型安全にコードを書くことができます.

特に js は恐ろしく柔軟に作られているライブラリが多いのでそれらを ts から使おうとするとこの辺の知識が必要になってくると思います.

またここでは Mapped Types とはなんぞやという説明は省いて実際によく使うテクニックをいろいろ書くので使いながら覚えてみてある程度分かってきたら公式ドキュメントを見てきちんと理解してみてください.

以下 TODO... φ(•ᴗ•๑)

ある interface が持っている特定の名前の key だけ抜き出して新たな型にする

ある interface が持っている特定の名前の key だけ除去して新たな型にする

ある interface が持っている function だけ抜き出す

ある interface が持っている function だけ除去する