Перейти к основному содержимому

Опциональная цепочка

· 4 мин. чтения
Майя Армянова ([@Zmayski](https://twitter.com/Zmayski)), разрыватель опциональных цепочек

Длинные цепочки доступа к свойствам в JavaScript могут быть рискованными, так как любое из них может оцениваться как null или undefined (также известные как "значения nullish"). Проверка существования свойства на каждом шаге легко превращается в глубокую вложенную структуру if-условий или длинное if-условие, воспроизводящее цепочку доступа к свойствам:

// Ошибочный вариант, может выдать ошибку.
const nameLength = db.user.name.length;

// Менее ошибочный, но сложнее читать.
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);

Представление оператора опциональной цепочки

Конечно, никто не хочет писать код таким образом, поэтому альтернатива будет желательна. Некоторые другие языки предлагают изящное решение этой проблемы с помощью функции, называемой "опциональная цепочка". Согласно недавнему предложению спецификации, "опциональная цепочка — это цепочка из одного или более доступов к свойствам и вызовов функций, первая из которых начинается с токена ?.".

Используя новый оператор опциональной цепочки, мы можем переписать приведенный выше пример следующим образом:

// Всё ещё проверяет ошибки и гораздо более читабельно.
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. Это потому, что пустая строка является ложным значением: она ведет себя как false в условии if. Оператор опциональной цепочки устраняет этот частый источник ошибок.

Дополнительные формы синтаксиса: вызовы и динамические свойства

Существует также версия оператора для вызова опциональных методов:

// Расширяет интерфейс с опциональным методом, который присутствует
// только для пользователей-администраторов.
const adminOption = db?.user?.validateAdminAndGetPrefs?.().option;

Синтаксис может показаться неожиданным, так как ?.() — это фактический оператор, который применяется к выражению перед ним.

Существует третье использование оператора, а именно опциональный доступ к динамическим свойствам, который осуществляется через ?.[]. Он либо возвращает значение, указанное аргументом в скобках, либо undefined, если объект отсутствует для получения значения. Вот возможный случай использования, следуя примеру выше:

// Расширяет возможности статического доступа к свойствам
// с динамически генерируемым именем свойства.
const optionName = 'опциональная настройка';
const optionLength = db?.user?.preferences?.[optionName].length;

Эта последняя форма также доступна для опционального индексирования массивов, например:

// Если `usersArray` равен `null` или `undefined`,
// тогда `userName` корректно оценивается как `undefined`.
const userIndex = 42;
const userName = usersArray?.[userIndex].name;

Оператор опциональной цепочки можно комбинировать с оператором слияния nullish ??, когда требуется значение по умолчанию, отличное от undefined. Это позволяет безопасно осуществлять доступ к свойствам в глубине с указанным значением по умолчанию, решая общий случай использования, который ранее требовал сторонних библиотек, таких как _.get lodash:

const object = { id: 123, names: { first: 'Алиса', last: 'Смит' }};

{ // С lodash:
const firstName = _.get(object, 'names.first');
// → 'Алиса'

const middleName = _.get(object, 'names.middle', '(нет второго имени)');
// → '(нет второго имени)'
}

{ // С опциональной цепочкой и слиянием nullish:
const firstName = object?.names?.first ?? '(нет первого имени)';
// → 'Алиса'

const middleName = object?.names?.middle ?? '(нет второго имени)';
// → '(нет второго имени)'
}

Свойства оператора опциональной цепочки

У оператора опциональной цепочки есть несколько интересных свойств: короткое замыкание, накопление и опциональное удаление. Давайте рассмотрим каждое из них на примере.

Короткое замыкание означает не выполнение остальной части выражения, если оператор опциональной цепочки возвращает значение раньше:

// `age` увеличивается только если `db` и `user` определены.
db?.user?.grow(++age);

Накопление означает, что более одного оператора опциональной цепочки может быть применено в последовательности обращения к свойствам:

// Опциональная цепочка может следовать за другой опциональной цепочкой.
const firstNameLength = db.users?.[42]?.names.first.length;

Тем не менее, будьте осторожны, используя более одного оператора опциональной цепочки в одной цепочке. Если значение гарантированно не является nullish, использование ?. для доступа к его свойствам нежелательно. В приведенном выше примере предполагается, что db всегда определен, но db.users и db.users[42] могут быть не определены. Если такой пользователь есть в базе данных, то names.first.length считается всегда определенным.

Опциональное удаление означает, что оператор delete может быть комбинирован с опциональной цепочкой:

// `db.user` удаляется только если `db` определен.
delete db?.user;

Больше подробностей можно найти в разделе Semantics предложения.

Поддержка опциональной цепочки