2020-07-24

TypeScript その1 型の基本

もはやフロントエンドを触る人にとってはTypeScriptは義務教育感があるので
今回はTypeScriptについて基本的な部分を調べてまとめておく

TypeScriptとは

所謂AltJSと呼ばれるもので、最近名前を聞かないが昔はCoffee Scriptが一世を風靡していた
AltJSで書いたコードを、素のJSに変換して使用する
(そんな面倒なことをするのは、素のJSが大規模開発に耐えられない貧弱な部分を補完してくれる恩恵があるから)

そんなAltJSの中で近年最も注目度が高く、実際に広く使われているのがTypeScriptである

型定義

素のJSでは型を使うことはできないが、TSには型が存在する
変数: 型 で型を定義する

let name: string = "hoge"
let count: number = 10
let users: string[] = ["taro", "kenji"] // let users: Array<string> = [〜] とも書ける
let users2: string[][] = [["hoge"], ["fuga"]] // 二次元配列
let multi: (number | boolean | string)[] = [1, false, "Tokyo"] // 複数種類の配列で、 | はorの意味
let multi2: [number, boolean, string] = [1, false, "hoge"] // tuple指定
let product: { name: string, price: number } = { name: "ルンバ", price: 98000 } // プロパティも指定したobject型
let product: {} = { name: "ルンバ", price: 98000 } // product: object じゃなくても実はよかったりする

型の種類は以下の通り

  • boolean(true, false)
  • number(1, 123, 9999)
  • string("hakozaru")
  • array([1, 2, 3])
  • tuple
    • 例えば ["hakozaru", 1234] こんな配列があった場合、 (string | number)[] という指定をしても、順番までは考慮されない([1234, "hakozaru"] でもエラーにならない)
    • なので const arr: [string, number] = ["hakozaru", 1234] と書くと順番まで型指定することができる
    • この指定の方法をタプル(tuple)と呼ぶ
  • any
  • void
  • null
  • undefined
  • never
  • object
  • unknown
  • 交差型(intersection)
    • 以下のように既存の型定義を再利用することができる

      type Prof1 = {
          name: string
      }
      
      type Prof2 = {
          age: number
      }
      
      // Prof1 | Prof2 のようにor条件も可能(その場合はnameかageがあればよい)
      type Prof3 = Prof1 & Prof2
      
      const user: Prof3 = {
          name: 'hako',
          age: 99,
      }
      
  • 共用体型(union)
    • 以下のように定義することをunion型と呼ぶ

      let value: number | string = 1
      value = "hako"
      
  • Literal
    • stringnumber では対象範囲が広すぎるような、ある特定の文字列、数値だけを代入可能にしたい時に使う

      // 日〜土までの文字列だけが入れられる
      let dayOfTheWeek: "日" | "月" | "火" | "水" | "木" | "金" | "土" = "日"
      dayOfTheWeek = "hako" // error!
      
      // 1〜12まで許可
      let month: 1 | 2 | 3| 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 = 1
      month = 13 // error!
      
  • 列挙型(enum)
    • Railsなどでもおなじみの型

      // { '0': 'Jan', 〜 '11': 'Dec', Jan: 0, 〜 Dec: 11 } みたいなオブジェクトが自動で定義される
      enum Months {
          Jan, Feb, Mar, Apr,
          May, Jun, Jul, Aug,
          Sep, Oct, Nov, Dec,
      }
      
      console.log(Months)     // { '0': 'Jan', 〜 '11': 'Dec', Jan: 0, 〜 Dec: 11 }
      console.log(Months[0])  // Jan
      console.log(Months.Jan) // 0
      
      // 0オリジンではなく、Janは1にしたい場合はオーバーライドもできる
      enum Months {
          Jan = 1, Feb, Mar, Apr,
          May,     Jun, Jul, Aug,
          Sep,     Oct, Nov, Dec,
      }
      
      console.log(Months[1])  // Jan
      console.log(Months.Jan) // 1
      
      // なお、Rubyのオープンクラスのように、必要に応じてあとから拡張することも可能
      enum Months {
          Hoge = 123,
          Fuga = 999,
      }
      

関数への型定義

関数に対しては戻り値の型を指定することができ、

function hoge(): void {
  console.log(987)
}

function fuga(): string {
  return "abc"
}

のように書く

なお、上のhoge関数の例では return を書いていないので、戻り値は存在しない
コンソールで見ると、undefinedと出るのでundefined型を指定したくなるが、
undefined型を指定した場合は、 return undefined または return と明示的にundefinedを返す必要がある

このように戻り値を指定していない場合はvoid型を指定しなければならない(anyでも可)
(ちなみに、void 型には undefined も含まれるので、 let hoge: void = undefined は問題なかったりする)

また、もし以下のように例外によって呼び出し元へ戻ってこないような関数の場合は never 型を使う

function hoge(msg: string): never {
  throw new Error(msg)
}

interface

以下のようなオブジェクトを変数に格納することを考える

{
  id: 123,
  title: "title ~~~",
  description: "hoge hoge hoge",
}

こいつはオブジェクトなので、定義は const data: object = { ... } を指定してあげれば、問題なくコンパイルを通過する

確かにオブジェクトなので指定は間違ってはいないが、オブジェクトの中のものまでチェックしないとエラーの原因になる
そこで使うのがinterfaceというもので、以下のように定義して使用する

// オブジェクトの定義ではないので、末尾にカンマは不要
// セミコロンなら入れてもOK
interface Data {
  id: number
  title: string
  description: string
}

const data: Data = { ... }

直感的に理解できると思うけど、オブジェクトの中身1つ1つに対して型を定義して、それに名前(Data)をつけている
言い換えると「オブジェクト型に名前をつけることができる」ということ
より正確に制約を課すことができるので、ガシガシ使っていきましょう

型エイリアス

type を使って型にエイリアスを設定することもできる

type Mojiretu = string
const moji: Mojiretu = 'aaa' // moji: stringと同義
console.log(mojiretu) // => aaa

// interfaceに似てる
type Profile = {
  name: string
  age: number
}
const prof: Profile = { name: "hako", age: 999 }
console.log(prof) // => { name: "hako", age: 999 }

// typeofで既存の定義を引っ張ってくることもできる
type Hoge = typeof Profile