はじめに
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の型ガードとして活用すると、安全に複数の型を扱える
- 配列や文字列で使う場合は、プロパティ名で判定していることを忘れずに