JavaScriptにおけるプロパティアクセスの長いチェーンは、エラーが発生しやすい可能性があります。なぜなら、チェーンのいずれかがnull
またはundefined
(「nullish」値とも呼ばれます)と評価される可能性があるからです。各ステップでプロパティの存在をチェックすると、簡単にif
文の深くネストされた構造、またはプロパティアクセスチェーンを複製する長いif
条件になってしまいます。
// Error prone-version, could throw.
const nameLength = db.user.name.length;
// Less error-prone, but harder to read.
let nameLength;
if (db && db.user && db.user.name)
nameLength = db.user.name.length;
上記は三項演算子を使用して表現することもできますが、可読性の向上にはあまり役立ちません。
const nameLength =
(db
? (db.user
? (db.user.name
? db.user.name.length
: undefined)
: undefined)
: undefined);
オプションチェーン演算子の導入 #
確かに、そのようなコードを書きたくはないでしょうから、代替手段を用意することが望ましいです。「オプションチェーン」と呼ばれる機能を使用することで、他の言語ではこの問題に対する洗練された解決策を提供しています。最近の仕様提案によると、「オプションチェーンは、1つ以上のプロパティアクセスと関数呼び出しのチェーンであり、最初のものはトークン?.
で始まります」。
新しいオプションチェーン演算子を使用すると、上記の例を次のように書き直すことができます。
// Still checks for errors and is much more readable.
const nameLength = db?.user?.name?.length;
db
、user
、またはname
がundefined
またはnull
の場合、どうなるでしょうか?オプションチェーン演算子を使用すると、JavaScriptはエラーをスローする代わりに、nameLength
をundefined
に初期化します。
この動作は、if (db && db.user && db.user.name)
によるチェックよりも堅牢であることに注意してください。たとえば、name
が常に文字列であることが保証されている場合はどうでしょうか? name?.length
をname.length
に変更できます。そして、name
が空の文字列であっても、正しい長さの0
が得られます。これは、空の文字列が偽の値であるためです。if
句ではfalse
のように動作します。オプションチェーン演算子は、この一般的なバグの原因を修正します。
追加の構文形式:呼び出しと動的プロパティ #
オプションのメソッドを呼び出すための演算子のバージョンもあります。
// Extends the interface with an optional method, which is present
// only for admin users.
const adminOption = db?.user?.validateAdminAndGetPrefs?.().option;
?.()
が実際の演算子であり、前の式に適用されるため、構文は予期しないものになる可能性があります。
演算子の3番目の使用方法、つまりオプションの動的プロパティアクセスがあります。これは?.[]
を介して行われます。括弧内の引数によって参照される値、または値を取得するオブジェクトがない場合はundefined
を返します。上記の例に倣って、考えられるユースケースを以下に示します。
// Extends the capabilities of the static property access
// with a dynamically generated property name.
const optionName = 'optional setting';
const optionLength = db?.user?.preferences?.[optionName].length;
この最後の形式は、配列のオプションのインデックス作成にも使用できます。例:
// If the `usersArray` is `null` or `undefined`,
// then `userName` gracefully evaluates to `undefined`.
const userIndex = 42;
const userName = usersArray?.[userIndex].name;
オプションチェーン演算子は、undefined
以外のデフォルト値が必要な場合、null合体演算子??
と組み合わせることができます。これにより、指定されたデフォルト値を使用して安全な深いプロパティアクセスが可能になり、lodashの_.get
などのユーザーランドライブラリを以前は必要としていた一般的なユースケースに対応できます。
const object = { id: 123, names: { first: 'Alice', last: 'Smith' }};
{ // With lodash:
const firstName = _.get(object, 'names.first');
// → 'Alice'
const middleName = _.get(object, 'names.middle', '(no middle name)');
// → '(no middle name)'
}
{ // With optional chaining and nullish coalescing:
const firstName = object?.names?.first ?? '(no first name)';
// → 'Alice'
const middleName = object?.names?.middle ?? '(no middle name)';
// → '(no middle name)'
}
オプションチェーン演算子のプロパティ #
オプションチェーン演算子には、いくつかの興味深いプロパティがあります。_短絡_、_スタッキング_、_オプションの削除_です。これらのそれぞれを例とともに見ていきましょう。
短絡とは、オプションチェーン演算子が早期に戻った場合、式の残りを評価しないことを意味します。
// `age` is incremented only if `db` and `user` are defined.
db?.user?.grow(++age);
スタッキングとは、プロパティアクセスシーケンスに複数のオプションチェーン演算子を適用できることを意味します。
// An optional chain may be followed by another optional chain.
const firstNameLength = db.users?.[42]?.names.first.length;
それでも、単一のチェーンで複数のオプションチェーン演算子を使用することについては、よく考えてください。値がnullishでないことが保証されている場合、?.
を使用してそのプロパティにアクセスすることはお勧めしません。上記の例では、db
は常に定義されていると見なされますが、db.users
とdb.users[42]
は定義されていない可能性があります。データベースにそのようなユーザーがいる場合、names.first.length
は常に定義されていると想定されます。
オプションの削除とは、delete
演算子をオプションチェーンと組み合わせることができることを意味します。
// `db.user` is deleted only if `db` is defined.
delete db?.user;
詳細は、提案の_セマンティクス_セクションにあります。