ミツモア Tech blog

「ミツモア」を運営する株式会社ミツモアの技術ブログです

Type Guardをきちんと使ってoptionalを使うのをやめよう

※ こちらはミツモアAdvent Calendar 2021の13日目の記事です。

こんにちは ミツモアのエンジニアの坂本です。

ミツモアは「リモートワークが増えてエアコンを綺麗にしたい」「引っ越しで出た不用品を回収してもらいたい」といった生活のあらゆるシーンであなたにぴったりの専門家を無料で探せるサービスですので、ぜひ気軽に使ってみてください!

meetsmore.com

今回はType Guardを使った型定義について話そうと思います。
ミツモアは元々JavaScriptで書かれていましたが、TypeScriptに移行しました。
移行するにあたり各所に型を書いていっていますが、型定義が緩い箇所が存在していてそれを定義し直したりしています。
今回はその一例を紹介しようと思います

Type Guardとは

Type Guard とは、特定の型かどうかをbooleanで返す関数の事です。

TypeScript: Documentation - Advanced Types

interface XXX {
 type: 'xxx'
}
const isXXType = (item: any): item is XXX =>  {
   return item.type === 'xxx'
}

if (isXXXType(item)) {
  /** XXX typeの時の処理 **/
  /** ここではitemの型はXXXとして推論される **/
}

isXXXType()で判定されたifの中ではitemはXXXとして型補完がされます。
実行時に動くコードになり実装の記述としても処理がわかりやすくなります。
注意点としてはType Guardの記述をミスると間違ったまま動くという点があります。

ミツモアのチャット

ミツモアでは依頼者と事業者の間でチャットができるのですが、チャットには色々なタイプがあります。

  • テキスト
  • 画像
  • ファイル
  • etc...

このタイプの情報をtypeフィールドに持っています。 それぞれのタイプに必要なデータを保持するフィールドがチャットのDBには存在しますが、あるtypeで利用されるフィールドは他のタイプのチャットからすると不要なフィールドです。 また、タイプを跨いで共有されているフィールドもあります。

ここでは仮にtypeがtextの時にはtextフィールドが、imageの時にはimageKeyがあるとします

ミツモアでは以下のように定義していました

interface Chat {
  _id: string
  type: 'text' | 'image' | 'file' | ...
  text?: string
  imageKey?: string
}

他のタイプでは存在しないフィールドであるが故に基本的にフィールドをoptionalで定義することになってしまっています。
また、知識として画像チャットの時にはimageKeyだけが存在することを実装者は知っていますが、型の情報からはこの知識が得られません。

そこでType Guardを使って安全に型を書くことにしました

interface TextChat {
  type: 'text'
  text: string
}

interface ImageChat {
  type: 'image'
  text: string
  imageKey: string
}

type Chat = TextChat | ImageChat

const isTextChat = (chat: any): chat is TextChat => chat.type === 'text'
const isImageChat = (chat: any): chat is ImageChat => chat.type === 'image'

const doSomething = (chat: Chat) => {
  chat.imageKey // 常にChat型に存在するわけではないのでtypeエラー
  if (isTextChat(chat)) {
    return chat.imageKey // TextChat型には存在しないのでtypeエラー
  }

  if (isImageChat(chat)) {
    return chat.imageKey
  }
}

型とTypeGuardをこのように定義することで、typeがtextなのにimageKeyにアクセスしようとすると型エラーにすることができます。 また、TypeGuardを使わないでアクセスしようとする時は当てはまりうる全ての型(ImageChatTextChat)に存在しない場合はエラーとなります。 そのため強制的にTypeGuardを使うことになり、型の安全性が自動的に高まります。

まとめ

optionalを使って緩く書かれてしまっている型を安全にわかりやすく定義できるTypeGuardについて説明しました。
何でもかんでもTypeGuardを使わないとダメなように書いてしまうと実装コストが上がってしまったり、JavaScript的には不要なコードを書かなければいけなかったりするのでうまく付き合っていくことが大事かなと思います。

現在、事業拡大を進めておりエンジニア・デザイナー・PdMを積極採用中です! ぜひWantedlyのリンクからカジュアル面談をしましょう。TwitterのDMでもお待ちしております。

www.wantedly.com