2022年3月1日

オプショナルチェイニング(Optional chaining) '?.'

A recent addition
This is a recent addition to the language. Old browsers may need polyfills.

オプショナルチェイニング ?. は中間のプロパティが存在しない場合でも、ネストされたオブジェクトプロパティへアクセスするための安全な方法です。

“存在しないプロパティ” 問題

チュートリアルを読んで JavaScript を学び始めたばかりであれば、まだ遭遇していなかもしれませんが、この問題は非常に一般的なことです。

例えば、ユーザに関する情報をもっている user オブジェクトがあるとしましょう。

ほとんどのユーザは user.address プロパティに住所を、user.address.street に通りを持っていますが、中には住所を指定していないユーザもいます。

そのようなユーザが住所を持っていないケースでは、user.address.street を取得しようとした場合に、エラーが発生します:

let user = {}; // ユーザがたまたま住所(address)をもっていない

alert(user.address.street); // Error!

これは予期した結果です。JavaScript はこのような動作をします。user.addressundefined なので、user.address.street の取得はエラーで失敗します。

多くの実用的なケースでは、この場面ではエラーが発生するのではなく、「通りがない」ということを意味する undeinfed が取得されることが好まれるでしょう。

…また、もう1つの例です。Web 開発では document.querySelector('.elem') のような特別なメソッド呼び出しにより、Web ページの要素に対応するオブジェクトを取得することができますが、そのような要素がない場合には null が返されます。

// querySelector(...)  の結果が null ならエラー
let html = document.querySelector('.my-element').innerHTML;

繰り返しになりますが、要素が存在しない場合、null への .innerHTML アクセスはエラーになります。また、要素がないことが正常である場合はエラーを避け、 html = null という結果を受け入れたい場合もあります。

どうすればよいでしょうか?

明白な解決策は、次のようにそのプロパティにアクセスする前に、if あるいは条件演算子 ? を使用して値を確認することでしょう。

let user = {};

alert(user.address ? user.address.street : undefined);

これはエラーなく機能しますが、まったくエレガントではありません。ご覧の通り、"user.address" がコードに2回登場します。深くネストされたプロパティでは、より多くの繰り返しが必要になるという問題を引き起こします。

例. user.address.street.name を取得してみましょう。

user.addressuser.address.street 両方のチェックが必要です。

let user = {}; // user は address を持っていません。

alert(user.address ? user.address.street ? user.address.street.name : null : null);

これはひどいですね。このようなコードは理解に苦しむかもしれません。

&& 演算子を使用することで、よりよい書き方をすることができます。

let user = {}; // user は address を持っていません

alert( user.address && user.address.street && user.address.street.name ); // undefined (エラーは起きません)

プロパティへのパス全体を AND することで、すべてのコンポーネントが確実に存在することが保証されますが(存在しない場合は評価が停止されます)、理想的ではありません。

ご覧の通り、プロパティ名は依然としてコードの中で重複しています。例えば、上のコードであれば user.address が3回登場します。

これがオプショナルチェイニング ?. が言語に追加された理由です。この問題を完全に解決するために。

オプショナルチェイニング

オプショナルチェイニング ?. は、?. の前の部分が undefined あるいは null であれば検査をストップし、undefined を返します。

この記事ではさらに、簡潔な表現として、null でも undefined でもない場合には、なにかが “存在” する、と述べています

つまり、value?.prop は:

  • value.prop として動作します(value が存在する場合)
  • そうでない場合(valueundefined/null)は、undefined を返します。

?. を使用して user.address.street に安全にアクセスする方法は次の通りです:

let user = {}; // ユーザは address を持たない

alert( user?.address?.street ); // undefined (エラーは起きません)

コードは短く簡潔になり、重複もありません。

たとえ user オブジェクトが存在しなくても、user?.address の address の読み取りは動作します:

let user = null;

alert( user?.address ); // undefined
alert( user?.address.street ); // undefined

?. 構文はその前の値をオプショナルにしますが、それ以上のことはしないことに注意してください。

例えば、user?.address.street.name?. により user を安全に null/undefined にすることができます(そして、このケースでは undefined を返します)が、これは user に対してのみです。その他のプロパティは通常の方法でアクセスされます。他のプロパティもオプショナルにしたい場合は、さらに .?. に置き換える必要があります。

オプショナルチェイニングを使いすぎないでください

何かが存在しなくてもOKな場合にのみ、?. を使用してください。

例えば、コードのロジックとして user オブジェクトは必須であるものの、address がオプションである場合、user.address?.street と書くべきでしょう。

したがって、プログラムミスにより user が未定義となった場合は、それに関するプログラミングエラーを確認し、修正します。そうせずに不用意に ?. を利用すると、コーディングエラーが適切でないときに沈黙し、デバッグが困難になる可能性があります。

?. の前の変数は定義されていなければなりません

user 変数が未定義の場合、user?.anything はエラーになります:

// ReferenceError: user is not defined
user?.address;

変数は宣言されていなければなりません(let/const/var user または関数のパラメータとして)。オプショナルチェイニングは、宣言済みの変数に対してのみ機能します。

短絡評価

前に述べたように、?. は左部分が存在しない場合には、即座に評価を停止(“short-circuits”)します。

したがって、そこになんらかの関数呼び出しや副作用があっても発生しません。

例:

let user = null;
let x = 0;

user?.sayHi(x++); // 何も起きません

alert(x); // 0, 値はインクリメントされていません。

他のケース: ?.(), ?.[]

オプショナルチェイニング ?. は演算子ではなく、特別な構文構造であり、関数や括弧と一緒でも機能します。

例えば、?.() は存在しない可能性のある関数を呼び出すときに使用されます。

以下のコードでは、ユーザによって admin メソッドがある場合とない場合があります:

let userAdmin = {
  admin() {
    alert("I am admin");
  }
};

let userGuest = {};

userAdmin.admin?.(); // I am admin

userGuest.admin?.(); // なにもしない (このようなメソッドはありません)

ここでは、両方の行で最初にドット . を使用して admin プロパティを取得しています。ユーザオブジェクトは必須であり、そこからは安全に読み取ることができるからです。

次に、?.() で左部分をチェックします: admin 関数が存在する場合は実行します(user1 の場合)。そうでなければ(user2 の場合)、評価はエラーなしで停止します。

ドット . の代わりに括弧 [] を使用してプロパティにアクセスしたい場合にも、?.[] 構文は機能します。前のケース同様、存在しない可能性のあるオブジェクトから安全にプロパティを読み取ることができます。

let key = "firstName";

let user1 = {
  firstName: "John"
};

let user2 = null;

alert( user1?.[key] ); // John
alert( user2?.[key] ); // undefined

delete と一緒に ?. を使用することもできます:

delete user?.name; // user が存在する場合、 user.name を削除します
?. を使用して、読み取りと削除を安全に行うことができますが、書き込みはできません

オプショナルチェイニング ?. は代入の左側では使用できません。

例:

// 以下のコードは、ユーザが存在する場合に user.name を書き込もうとするものです

user?.name = "John"; // エラー、動作しません
// undefined = "John" と評価されるためです

サマリ

?. 構文には3つの形式があります:

  1. obj?.propobj が存在する場合、obj.prop を返します。そうでない場合は undefined です。
  2. obj?.[prop]obj が存在する場合、obj[prop] を返します。そうでない場合は undefined です。
  3. obj?.method()obj が存在する場合、obj.method() を呼び出します。そうでない場合は undefined を返します。

見ての通り、どれも素直で使いやすいものばかりです。?. は、左側の部分が null/undefined かどうかをチェックし、そうでない場合は評価を続行できるようにします。

?. のチェインにより、ネストされたプロパティに安全にアクセスすることができます。

そうであっても、左側の部分が存在しないことが許容される場合にのみ、慎重に ?. を適用する必要があります。プログラミングエラーが起こった場合に、それを隠してしまわないようにするためです。

チュートリアルマップ