オプションチェーン?.
は、中間のプロパティが存在しない場合でも、ネストされたオブジェクトプロパティに安全にアクセスする方法です。
「存在しないプロパティ」問題
チュートリアルを読み始めてJavaScriptを学び始めたばかりであれば、おそらくこの問題はまだ発生していないかもしれませんが、これは非常に一般的な問題です。
例として、ユーザーに関する情報を保持するuser
オブジェクトがあるとします。
ほとんどのユーザーは、user.address
プロパティに住所を持ち、番地はuser.address.street
にありますが、一部のユーザーは住所を提供していません。
そのような場合、user.address.street
を取得しようとすると、住所のないユーザーだった場合、エラーが発生します。
let user = {}; // a user without "address" property
alert(user.address.street); // Error!
これは想定された結果です。JavaScriptはこのように動作します。 user.address
がundefined
であるため、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
として機能し、 - そうでない場合(
value
がundefined/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つの形式があります
- `obj?.prop` – `obj`が存在する場合は`obj.prop`を返し、そうでない場合は`undefined`を返します。
- `obj?.[prop]` – `obj`が存在する場合は`obj[prop]`を返し、そうでない場合は`undefined`を返します。
- `obj.method?.()` – `obj.method`が存在する場合は`obj.method()`を呼び出し、そうでない場合は`undefined`を返します。
ご覧のとおり、すべてが簡単で使いやすく、`?.`は左側の部分を`null/undefined`でチェックし、そうでない場合は評価を続行できるようにします。
`?.`のチェーンにより、ネストされたプロパティに安全にアクセスできます。
それでも、コードロジックに従って左側の部分が存在しないことが許容される場合にのみ、`?.`を慎重に適用する必要があります。発生した場合に、プログラミングエラーが隠されないようにするためです。
コメント