まくろぐ

TypeScript で undefined/null をうまく扱う (nullish coalescing (??), optional chaining (?.))

更新:
作成:

Null 合体演算子 (??)

ES2020 で Null 合体演算子 (Nullish Coalescing Operator)?? が導入されました。

☝️ nullish とは? JavaScript において nullish とは、「undefined あるいは null」を示します。 coalescing には、「癒合、合体」という意味があります。 よって、nullish coalescing は、「null っぽかったら合体させるよ」という意味になります。

?? 演算子を使うと、ある変数の値が undefined(あるいは null)だったときの代替となる値を指定することができます。 つまり、

A ?? B

は、次のように記述するのと同等です。

A != undefined ? A : B

下記は、?? 演算子の振る舞いの一覧です。 ?? 演算子はあくまで undefinednull だけを判別するものであって、左側に偽となる値(false や空文字)をおいた場合は、その値がそのまま使われることに注意してください。

// 右側の値が(代わりに)使われるパターン
const a = undefined ?? 'default'  //=> 'default'
const b = null ?? 'default'       //=> 'default'

// 左側の値が(そのまま)使われるパターン
const c = '' ?? 'default'     //=> ''
const d = 'aaa' ?? 'default'  //=> 'aaa'
const e = 1 ?? 100            //=> 1
const f = 0 ?? 200            //=> 0
const g = true ?? 'default'   //=> true
const h = false ?? 'default'  //=> false
const i = [] ?? [1, 2, 3]     //=> []

Nullish Coaescing Operator は、省略可能なプロパティを持つオブジェクトを参照するときに便利です。 次の dumpPoint 関数は、渡された Enemy オブジェクトの point プロパティの値をしますが、この値が未設定の場合はデフォルト値の 100 を表示します。

type Enemy = {
  name: string
  point?: number  // このプロパティ値は省略可能
}

function dumpPoint(enemy: Enemy) {
  console.log(enemy.point ?? 100)
}

dumpPoint({ name: 'Enemy1', point: 0 })   //=> 0
dumpPoint({ name: 'Enemy2', point: 50 })  //=> 50
dumpPoint({ name: 'Enemy3' })             //=> 100

従来はこのような処理のために短絡演算子 (||) がよく使われていましたが、下記のように偽値となる値(0 や false)がうまく扱えないという問題がありました。 今後は、?? を使えば OK です。

よくない例
console.log(enemy.point || 100)  // point=0 の時に 100 になってしまう

デフォルト値に関しては、次のように分割代入時に設定する方法も健在です。 用途によって使い分けましょう。

function dumpPoint(enemy: Enemy) {
  const { name, point = 100 } = enemy
  console.log(name)
  console.log(point)
}

オプショナルチェイニング演算子 (?:)

ES2020 では、オプショナルチェイニング演算子 (Optional Chaining Operator)?. も導入されています。 これは、undefined(あるいは null)であるかもしれないオブジェクトのプロパティを参照するときに使用します。

A?.foo()

とすると、オブジェクト Aundefined(あるいは null)の場合に undefined になり、それ以外の場合に A のプロパティ foo が参照されます。 つまり、上記は下記と同様です。

A != undefined ? A.foo() : undefined

下記は Optional Chaining の具体的な使用例です。 User オブジェクトは入れ子の形で Address オブジェクトを保持することができますが、そのための address プロパティは省略可能なものとして定義されています。 このように、入れ子になったオブジェクトのプロパティを参照するケースで、Optional Chaining は威力を発揮します。

type Address = {
  country: string
  city: string
}

type User = {
  name: string
  address?: Address
}

function dumpCity(user: User) {
  // ここでオプショナルチェイニングを使用 (`•ω•´)
  const city = user.address?.city ?? 'UNKNOWN_CITY'
  console.log(city)
}

dumpCity({ name: 'User1', address: { country: 'Japan', city: 'Tokyo' } })
dumpCity({ name: 'User2' })
実行結果
Tokyo
UNKNOWN_CITY

ポイントは次の部分で、Optional Chaining (?.) と、前述の Nullish Coalescing (??) を組み合わせて使っています。

const city = user.address?.city ?? 'UNKNOWN_CITY'

user.addressAddress オブジェクトが設定されている場合は city プロパティを参照し、user.address が設定されていない場合(undefined のとき)は、代わりに UNKNOWN_CITY という値を使うようにしています。 単純に user.address.city と繋げて書くと、user.addressundefined のときに不正な参照になってしまいます。

このイディオムは、オブジェクトの入れ子構造がもっと深い場合でもうまく機能します。

// いずれかのプロパティが undefined になった時点で NO_NAME になる
const name = obj.aaa?.bbb?.ccc?.name ?? 'NO_NAME'

また、ある関数型プロパティが設定されている場合だけ呼び出したいときは次のように記述できます。

// fix プロパティに関数がセットされている場合のみ呼び出す
sentence.fix?.()

関連記事

まくろぐ
サイトマップまくへのメッセージ