Прежде чем перейти к статье, хочу вам представить, экономическую онлайн игру Brave Knights, в которой вы можете играть и зарабатывать. Регистируйтесь, играйте и зарабатывайте!
Автор фото — Miguel Á. Padriñán.
Доброго времени суток, друзья!
Представляю Вашему вниманию перевод статьи Sam Sedighian «Optional Chaining, Null Coalescing and How They Will Change the Way You Write Code».
Опциональная цепочка, объединение с null, и как они меняют подход к написанию кода
Более безопасный и эргономичный способ доступа к объекту.
Если Вы следите за релизами TypeScript, то знаете, что опциональная цепочка (Optional Chaining) и объединение с null (Null Coalescing) были представлены в TypeScript 3.7. Также эти фичи по умолчанию включены в Babel 7.8.0. Это одни из главных претендентов на роль особенностей JavaScript. В настоящее время они находятся на 4 и 3 стадиях рассмотрения, соответственно (23 января был принят стандарт ECMAScript 2020, где есть и опциональная цепочка, и объединение с null, и еще парочка занимательных вещей; я в курсе, что недавно на Хабре вышла статья-перевод про особенности, которые привнес в JS ES2020, однако, полагаю, парочка лишних примеров не повредит — прим. пер.).
Опциональная цепочка (Optional Chaining)
Довольно часто нам приходится обращаться к глубоко вложенному свойству объекта. Если Вы написали 100 строк не очень качественного кода, это может стать причиной Uncaught TypeError.
const data = {}
data.user.id // Uncaught TypeError: Cannot read property 'id' of undefined
// получить такую ошибку в ответ на обращение к свойству объекта - обычное дело
Для дальнейших примеров мы будем использовать этот псевдокод (ложный ответ сервера):
{
'url': 'https://api.github.com/repos/sedighian/Hello-World/pulls/12',
'number': 12,
'state': 'open',
'title': 'Amazing new feature',
'user': {
'login': 'sedighian',
'id': 123234
},
'labels': [{
'id': 208045946,
'name': 'bug',
}]
}
Пытаясь избежать Uncaught TypeError, и получить значение id нам приходится «устраивать пляски» (do some dance). Подход, который мы использовали раньше, заключался в проверке истинности объекта на каждом уровне вложенности. Данный шаблон больше похож на условный оператор, возвращающий логическое значение, чем на способ обращения к свойству, но это самый чистый и безопасный путь, которым мы располагали до настоящего времени:
const userId = data && data.user && data.user.id
// с массивами и функциями
const label = data && data.labels && data.labels[0]
const someFuncRes = navigator && navigator.serviceWorker && window.serviceWorker()
Либо, если Вы предпочитаете деструктуризацию:
const { user: { id } = {} } = data || {}
// общий паттерн состоял в создании временных переменных
const { user } = data || {}
const { id } = user || {}
Более эргономичный способ заключается в использовании Lodash или Ember:
// lodash
import _ from 'lodash'
_.get(data, 'user.id')
// ember
import { get } from '@ember/object'
get(data, 'user.id')
Как нам сделать тоже самое с помощью опциональной цепочки?
const userId = data?.user?.id
// с массивами и функциями
const label = data?.labels?.[0]
const someFuncRes = someFunc?.()
Объединение с null (Null Coalescing)
Когда значением свойства объекта, к которому мы обращаемся, является null или undefined, мы используем значение по умолчанию. Раньше для этого использовался оператор || (логическое или).
Если мы хотим по умолчанию записывать sedighian в значение свойства login, мы делаем следующее:
// как мы делали это раньше
data && data.user && data.user.login || 'sedighian'
// с помощью опциональной цепочки
data?.user?.login || 'sedighian'
// с помощью опциональной цепочки и объединения с null
data?.user?.login ?? 'sedighian'
Второй и третий пример похожи. В чем преимущество объединения с null? Объединение с null оценивает значение справа только в случае, если левая часть равна undefined или null. Это дает нам некоторую защиту от случайных результатов, когда мы работаем с действительными (валидными), но ложными значениями.
Предположим, мы хотим вернуть '' (пустую строку), false или 0. С оператором || этого сделать не получится, поскольку он вернет правую часть. В этом случае нам пригодится объединение с null:
// если data.user.alias является пустой строкой, которую мы хотим получить
data?.user?.alias ?? 'code ninja' // ''
data?.user?.alias || 'code ninja' // code ninja
// если data.user.volumePreference = 0
data?.user?.volumePreference ?? 7 // 0
data?.user?.volumePreference || 7 // 7
// если data.user.likesCats = false
data?.user?.likesCats ?? true // false
data?.user?.likesCats || true // true
В качестве альтернативы можно использовать сторонние библиотеки, а в случае с Ember — встроенную утилиту:
// lodash
import _ from 'lodash'
_.get(data, 'user.likesCats', true)
// ember
import { getWithDefault } from '@ember/object'
getWithDefault(data, 'user.likesCats', true)
Не забывайте, что объединение с null — это больше, чем значение переменной по умолчанию. Это альтернативный способ выполнения блока кода при отсутствии соответствующих значений:
const temp = { celsius: 0 }
temp?.fahrenheit ?? setFahrenheit(temp)
О чем следует помнить?
Помните о порядке следования символов в опциональной цепочке (сначала вопросительный знак, затем точка):
data.?user // Uncaught SyntaxError: Unexpected token '?'
data?.user // ok
Опциональная цепочка не защищает от вызова несуществующей функции:
data?.user() // Ungaught TypeError: user is not a function
Объединение с null не идентично lodash.get или EmberObject.getWithDefault. Основное отличие состоит в том, как объединение c null справляется со значением «null»:
const data = { user: { interests: null } }
// lodash
import _ from 'lodash'
_.get(data, 'user.interests', 'knitting') // null
// ember
import { get } from '@ember/object'
getWithDefault(data, 'user.interests', 'knitting') // null
// объединение с null
data?.user?.interests ?? 'knitting' // knitting
Благодарю за внимание. Всех благ.