0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【ポケモンで学ぶ】JavaScriptのin演算子を完全理解!プロパティ判定でエラーを回避する方法

Posted at

はじめに

JavaScriptやTypeScriptでまあまあ見かける "in" 演算子。
なんとなく使ってたけどちゃんとは理解してなかったので、ポケモンを例にまとめてみた。
参考:みんな大好きMDNのドキュメント

ざっくりとした説明

"in" は、指定したプロパティがオブジェクトに存在するかを判定する演算子。
存在すればtrue, なければfalseを返す。

const pikachu = { no: 25, name: 'Pikachu', type: ['Electric'], height: 0.4, weight: 6.0 };

// プロパティ名 "type" が "pikachu"オブジェクトに含まれるため、true
console.log('type' in pikachu);

// プロパティ名 "terastalType" は "pikachu"オブジェクトに含まれないため、false
console.log('terastalType' in pikachu);

使い所

"in" が特に力を発揮するのはTypeScriptでの型ガードとしての利用。
ポケモンのデータを扱う例で解説する。

interface Pokemon {
    no: number
    name: string
    type: string[]
    height: number
    weight: number
}

interface ZA extends Pokemon {
    megaEvolution: boolean // メガシンカできるか
}

interface SV extends Pokemon {
    terastalType: string // テラスタルタイプ
}

const pikachu: ZA  = {
    no: 25,
    name: 'Pikachu',
    type: ['Electric'],
    height: 0.4,
    weight: 6.0,
    megaEvolution: false
}

const raichu: ZA = {
    no: 26,
    name: 'Raichu',
    type: ['Electric'],
    height: 0.8,
    weight: 30.0,
    megaEvolution: true
}

const pamo: SV = {
    no: 921,
    name: 'Pamo',
    type: ['Electric'],
    height: 0.3,
    weight: 2.5,
    terastalType: 'Electric'
}

const electricPokemons = [pikachu, raichu, pamo]

これはエラーになる

// エラー: プロパティ 『megaEvolution』 は型 『ZA | SV』 に存在しません。
const megaEvolutionPokemon = electricPokemons.filter((pokemon)=> pokemon.megaEvolution)

配列にはZA型とSV型が混在しているため、すべてのポケモンに"megaEvolution"プロパティがあるとは限らない。存在しない可能性のあるプロパティにアクセスしようとしているため、TypeScriptがエラーを出す。

"in" 演算子で型ガードを使う

const megaEvolutionPokemon 
= electricPokemons.filter((pokemon)=> 'megaEvolution' in pokemon && pokemon.megaEvolution)

'megaEvolution' in pokemonで先にプロパティの存在を確認することで、TypeScriptが「このポケモンはZA型だ」と理解してくれる。これで安全にプロパティにアクセスできる。

勘違いしやすい点

in演算子の右側には、オブジェクトを指定する必要がある。
配列はarrayObjectなので、 "array.length" など、アクセスできるプロパティはtrueになる。
配列の要素はプロパティではないため、falseになる。

const pokemon = ['Pichu', 'Pikachu', 'Raichu']

console.log('length' in pokemon) // true(配列のプロパティ)
console.log('0' in pokemon) // true(インデックスもarray[0]とアクセスできるのでプロパティとして扱われる)

console.log('Pichu' in pokemon) // false(要素の値はプロパティ名ではない)

通常のオブジェクトも同じく勘違いしやすいので注意。
inがtrueになるのはObject.[property] でアクセスできるプロパティのみ

const pikachu: ZA  = {
    no: 25,
    name: 'Pikachu',
    type: ['Electric'],
    height: 0.4,
    weight: 6.0,
    megaEvolution: false
}

// pikachu.type とアクセスできるのでtrue
console.log('type' in pikachu)

// pikachu.Pikachu とはアクセスできないのでfalse
console.log('Pikachu' in pikachu)

リテラルとオブジェクトの違い

const pamo = 'Pamo'

// 型 『string』 は型 『object』 に代入できません。
console.log('length' in pamo)

const morpeko = new String('Morpeko')
console.log('length' in morpeko) // true

文字列リテラルはプリミティブ型なので "in" 演算子は使用不可。
一方、new String() で作成したStringオブジェクトは使える。

プリミティブ型とオブジェクトの違いはこちらの書籍でわかりやすく解説されている。

まとめ

  • "in" 演算子はオブジェクトにプロパティが存在するかを判定する
  • TypeScriptの型ガードとして活用すると、安全に複数の型を扱える
  • 配列や文字列で使う場合は、プロパティ名で判定していることを忘れずに
0
0
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
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?