2
1

はじめに

プロダクトのソースコードで見慣れないコードを見つけました。
何をやっているのか文法から理解できなかったので先輩に聞いてみると、「型ガードで型を絞り込んでいるね」と教えていただきました。
なんのことかわからなかったので調べてみました。

わからなかったコード

前後のソースコードは省略しています。
わからなかったのは、'id' in currentMenuという文法です。

if ('id' in currentMenu && currentMenu.id === targetId ) {
   return //...省略
}

なんとなく、「currentMenuにidプロパティが存在するなら、idプロパティにアクセスする」みたいな意味かと推測しました。

試しに消してみましょう。

if ( currentMenu.id === targetId ) {
   return //...省略
}

プロパティ 'id' は型 'LimitedMenu | StandardMenu' に存在しません。
プロパティ 'id' は型 'LimitedMenu' に存在しません。ts(2339)

エラーが出てしまいました😱

私:「なんだこのエラーは??????プロパティがないってどういうこと???????

さらに、currentMenu.まで書いてVSコードの推論を見てみたところ、確かにidが出てきませんでした。どうやら本当にidプロパティが存在していないようです。

何が起きているのかわからなかったので先輩に質問して、「制御フロー分析」と「型ガード」を教えていただきました。

JavaScriptのin演算子

まずは、使われているin演算子について。

これはJavaScriptの文法で、指定したオブジェクトに特定のプロパティが存在するかどうかをチェックすることができます。
プロパティが存在する場合はtrue、存在しないならfalseを返します。

const user = { name: 'odeko', id: '@odendayoko' };

console.log('id' in user);
// Expected output: true

詳細はこちらをご覧ください。

よって、'id' in currentMenuと書いた場合、currentMenuinプロパティが存在すればtrue、存在しない場合はfalseと判定されます。

制御フロー分析(Control Flow Analysis, CFA)

ここからはTypeScriptのお話です。
制御フロー分析という機能があるらしいです。

TypeScriptはifやforループなどの制御フローを分析することで、コードが実行されるタイミングでの型の可能性を判断しています。

下記の例で考えてみます。

monthの型はstring | numberです。if文の分岐に入るまでは、monthがstring型かnumber型のどちらかが決まっていません。この場合、string型でしか使えないメソッドへアクセスしようとするとTypeScriptのエラーが出ます。
そこで、if文でmonthの型をstring型に確定させるように条件分岐させます。これにより、メソッド実行時の型がstringに決まるので、エラーは発生しません。

const showMonth = (month: string | number) => {
  // ここでメソッドにアクセスすると、string型かnumber型か決まらないのでエラーになる
  // console.log(month.padStart(2, "0"))
  
  if (typeof month === "string") {
    // string型に決まるのでエラーは起きない
    console.log(month.padStart(2, "0"));
  }
}

こんな感じで、TypeScriptはコードの特定の部分で変数がどの型であるかを推論してくれるみたいです。

型ガード

上記の例で、if(typeof month === "string")という条件分岐で変数の型を判定して型の絞り込みを行っています。このような型の制御を型ガードと呼ぶらしいです。

代表的なのはtypeof演算子を使った型ガードです。
他にも色々種類があるようなので詳しくはこちらをご覧ください。

今回は、in演算子を使って型を絞り込んでいたことがわかりました。

コードを読み解いてみる

ではわからなかったコードに戻ってみましょう。

interface LimitedMenu {
  id: number
  name: string
  url: string
}

interface StandardMenu {
  name: string
  url: string
}

// currentPhoto: LimitedMenu | StandardMenu
if ('id' in currentMenu && currentMenu.id === targetId ) {
   return //...省略
}

currentMenuの型はLimitedMenu | StandardMenuです。型を確認すると、LimitedMenuにはidプロパティが存在しますが、StandardMenuには存在しません。

よって、'id' in currentMenu &&とすることで、右項ではcurrentMenuの型がLimitedMenuに決まります。LimitedMenu型なら、idプロパティが存在するのでアクセスしてもエラーは起きません。

currentMenu.idとした場合にエラーが発生したのは、currentMenuidプロパティを持たないStandardMenu型をとる可能性があるからです。型として存在しないプロパティにアクセスすることはできません。

終わりに

簡単そうに見えて意外と奥が深い内容でした!
ユニオン型で、各要素の型のプロパティに違いがある場合は注意が必要ですね💡

2
1
0

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
2
1