Доброго времени суток, друзья!
Если Вы следите за релизами TypeScript, то знаете, что опциональная цепочка (Optional Chaining) и объединение с null (Null Coalescing) были представлены в TypeScript 3.7. Также эти фичи по умолчанию включены в Babel 7.8.0. Это одни из главных претендентов на роль особенностей JavaScript. В настоящее время они находятся на 4 и 3 стадиях рассмотрения, соответственно (23 января был принят стандарт ECMAScript 2020, где есть и опциональная цепочка, и объединение с null, и еще парочка занимательных вещей; я в курсе, что недавно на Хабре вышла статья-перевод про особенности, которые привнес в JS ES2020, однако, полагаю, парочка лишних примеров не повредит — прим. пер.).
Довольно часто нам приходится обращаться к глубоко вложенному свойству объекта. Если Вы написали 100 строк не очень качественного кода, это может стать причиной Uncaught TypeError.
Для дальнейших примеров мы будем использовать этот псевдокод (ложный ответ сервера):
Пытаясь избежать Uncaught TypeError, и получить значение id нам приходится «устраивать пляски» (do some dance). Подход, который мы использовали раньше, заключался в проверке истинности объекта на каждом уровне вложенности. Данный шаблон больше похож на условный оператор, возвращающий логическое значение, чем на способ обращения к свойству, но это самый чистый и безопасный путь, которым мы располагали до настоящего времени:
Либо, если Вы предпочитаете деструктуризацию:
Более эргономичный способ заключается в использовании Lodash или Ember:
Как нам сделать тоже самое с помощью опциональной цепочки?
Когда значением свойства объекта, к которому мы обращаемся, является null или undefined, мы используем значение по умолчанию. Раньше для этого использовался оператор || (логическое или).
Если мы хотим по умолчанию записывать sedighian в значение свойства login, мы делаем следующее:
Второй и третий пример похожи. В чем преимущество объединения с null? Объединение с null оценивает значение справа только в случае, если левая часть равна undefined или null. Это дает нам некоторую защиту от случайных результатов, когда мы работаем с действительными (валидными), но ложными значениями.
Предположим, мы хотим вернуть '' (пустую строку), false или 0. С оператором || этого сделать не получится, поскольку он вернет правую часть. В этом случае нам пригодится объединение с null:
В качестве альтернативы можно использовать сторонние библиотеки, а в случае с Ember — встроенную утилиту:
Не забывайте, что объединение с null — это больше, чем значение переменной по умолчанию. Это альтернативный способ выполнения блока кода при отсутствии соответствующих значений:
Помните о порядке следования символов в опциональной цепочке (сначала вопросительный знак, затем точка):
Опциональная цепочка не защищает от вызова несуществующей функции:
Объединение с null не идентично lodash.get или EmberObject.getWithDefault. Основное отличие состоит в том, как объединение c null справляется со значением «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
Благодарю за внимание.
