2022年4月14日

オプションチェーン `?.`

最近の追加事項
これは言語に最近追加されたものです。古いブラウザではポリフィルが必要になる場合があります。

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

「存在しないプロパティ」問題

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

例として、ユーザーに関する情報を保持するuserオブジェクトがあるとします。

ほとんどのユーザーは、user.addressプロパティに住所を持ち、番地はuser.address.streetにありますが、一部のユーザーは住所を提供していません。

そのような場合、user.address.streetを取得しようとすると、住所のないユーザーだった場合、エラーが発生します。

let user = {}; // a user without "address" property

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

これは想定された結果です。JavaScriptはこのように動作します。 user.addressundefinedであるため、user.address.streetを取得しようとするとエラーが発生します。

多くの実際的なケースでは、ここでエラーではなくundefined(「番地なし」を意味する)を取得することを好みます。

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

// document.querySelector('.elem') is null if there's no element
let html = document.querySelector('.elem').innerHTML; // error if it's null

繰り返しますが、要素が存在しない場合、null.innerHTMLプロパティにアクセスしようとするとエラーが発生します。要素がないことが正常な場合は、エラーを回避し、結果としてhtml = nullを受け入れるようにします。

どうすればこれを解決できるでしょうか?

明白な解決策は、プロパティにアクセスする前に、ifまたは条件演算子?を使用して値をチェックすることです。次のようにします。

let user = {};

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

これは機能し、エラーはありません…しかし、あまり洗練されていません。ご覧のとおり、"user.address"がコードに2回表示されます。

document.querySelectorでは、同じことが次のようになります。

let html = document.querySelector('.elem') ? document.querySelector('.elem').innerHTML : null;

要素検索document.querySelector('.elem')が実際にはここで2回呼び出されていることがわかります。良くありません。

より深くネストされたプロパティの場合、より多くの繰り返しが必要になるため、さらに醜くなります。

たとえば、同様の方法でuser.address.street.nameを取得してみましょう。

let user = {}; // user has no address

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

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

&&演算子を使用すると、少し良い書き方があります。

let user = {}; // user has no address

alert( user.address && user.address.street && user.address.street.name ); // undefined (no error)

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

ご覧のとおり、プロパティ名はコード内で重複しています。たとえば、上記のコードでは、user.addressが3回表示されます。

そのため、オプションチェーン?.が言語に追加されました。この問題を一度に解決するためです!

オプションチェーン

オプションチェーン?.は、?.の前の値がundefinedまたはnullの場合、評価を停止し、undefinedを返します。

この記事では、以降、簡潔にするために、nullでもundefinedでもない場合、何かが「存在する」と言います。

言い換えれば、value?.prop

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

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

let user = {}; // user has no address

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

コードは短くてクリーンで、重複はまったくありません。

document.querySelectorの例を次に示します。

let html = document.querySelector('.elem')?.innerHTML; // will be undefined, if there's no element

userオブジェクトが存在しない場合でも、user?.addressを使用してアドレスを読み取ることができます。

let user = null;

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

注意:?.構文は、その前の値をオプションにしますが、それ以降はオプションになりません。

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

オプションチェーンを使いすぎないでください

何かが存在しないことが問題ない場合にのみ、?.を使用する必要があります。

たとえば、コードロジックによるとuserオブジェクトが存在する必要があるが、addressはオプションである場合、user.address?.streetと書くべきであり、user?.address?.streetと書くべきではありません。

その後、userがたまたま未定義の場合、それに関するプログラミングエラーが表示され、修正されます。そうでない場合、?.を使いすぎると、コーディングエラーが適切でない場所で抑制され、デバッグがより困難になる可能性があります。

?.の前にある変数は宣言されている必要があります

変数userがまったくない場合、user?.anythingはエラーをトリガーします

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

変数は宣言する必要があります(例:let/const/var userまたは関数パラメータとして)。オプションチェーンは、宣言された変数に対してのみ機能します。

ショートサーキッティング

前述のように、?.は、左側の部分が存在しない場合、評価をすぐに停止(「ショートサーキット」)します。

したがって、?.の右側にさらに関数呼び出しまたは操作がある場合、それらは実行されません。

例えば

let user = null;
let x = 0;

user?.sayHi(x++); // no "user", so the execution doesn't reach sayHi call and x++

alert(x); // 0, value not incremented

その他のバリアント:?.()、?. []

オプションチェーン?.は演算子ではなく、関数と角かっこでも機能する特別な構文構造です。

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

以下のコードでは、一部のユーザーにはadminメソッドがあり、一部のユーザーにはありません。

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

let userGuest = {};

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

userGuest.admin?.(); // nothing happens (no such method)

ここでは、両方の行で、最初にドット(userAdmin.admin)を使用してadminプロパティを取得します。これは、userオブジェクトが存在すると想定しているため、安全に読み取ることができるためです。

次に、?.()は左側の部分をチェックします。admin関数が存在する場合、それは実行されます(userAdminの場合)。それ以外の場合(userGuestの場合)、評価はエラーなしで停止します。

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

let key = "firstName";

let user1 = {
  firstName: "John"
};

let user2 = null;

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

また、`?.`を`delete`と一緒に使用することもできます。

delete user?.name; // delete user.name if user exists
安全な読み取りと削除には`?.`を使用できますが、書き込みには使用できません

オプションチェーン`?.`は、代入の左側では使用できません。

例えば

let user = null;

user?.name = "John"; // Error, doesn't work
// because it evaluates to: undefined = "John"

まとめ

オプションチェーン`?.`構文には3つの形式があります

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

ご覧のとおり、すべてが簡単で使いやすく、`?.`は左側の部分を`null/undefined`でチェックし、そうでない場合は評価を続行できるようにします。

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

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

チュートリアルマップ

コメント

コメントする前にこれを読んでください…
  • 改善のための提案がある場合は、コメントする代わりにGitHubのissueまたはプルリクエストを送信してください。
  • 記事の内容が理解できない場合は、詳しく説明してください。
  • 数語のコードを挿入するには、`<code>`タグを使用します。数行の場合は、`<pre>`タグで囲みます。10行を超える場合は、サンドボックス(plnkrjsbincodepen…)を使用します。