Деструктурирование объектов TypeScript и вы
Javascript существует уже довольно давно, впервые он появился почти 30 лет назад. Благодаря своей богатой истории, за эти годы он приобрел довольно большую функциональность.
Совсем недавно, примерно в 2012 году, в TypeScript была предпринята попытка ввести типы в JavaScript. К счастью, поскольку разработчики - это относительно неразборчивая группа (и они легко соглашаются со всем), никогда не было большого ажиотажа по поводу JavaScript vs. Дебаты на машинописном языке. Нет.
Независимо от того, являетесь ли вы членом клуба typed club или нет, в TypeScript есть функция, которая может значительно облегчить жизнь, - это деструктурирование объектов. Деструктурирование объектов в TypeScript - это немного странная процедура; иногда вы можете прислушаться к тому, что происходит, и принять на себя определенную функциональность.
Например, вы знаете, что такое обещание или наблюдаемый объект. Но разрушение? Это не дает достоверного представления о том, что это такое на самом деле.
Что такое деструктурирование объекта?
Я буквально годами не использовал деструктурирование объектов, так что, если вы не сразу осознаете, что это такое и почему вы хотите его использовать, это нормально.
Однако теперь, когда я знаю об этом, это значительно облегчает мою жизнь при работе с операторами, которые принимают входные данные и генерируют другие переменные. Например, наблюдаемая величина может выдавать множество значений, которые передаются по каналу, и с каждым оператором может стать сложнее определить, какая именно переменная находится в последующих ответах.
К этому конкретному варианту использования мы вернемся чуть позже. А пока давайте начнем медленно и воспользуемся простыми примерами, чтобы понять, как они работают.
Рассмотрим этот скромный набор:
let simpleArray = [1,2,3,4,5];
Если бы мы хотели получить первые два значения этого массива, обычно мы могли бы использовать slice или какой-либо другой оператор. Но при деструктурировании мы можем сделать это вместо этого:
const [a,b] = simpleArray
Кажется странным присваивать биты массива нашему const, но на самом деле мы просто извлекаем первые два значения этого массива и присваиваем им значения a и b. Если бы мы хотели получить доступ к оставшимся значениям, мы могли бы даже сделать что-то вроде этого:
const [a, b, ...remaining] = simpleArray
Это хороший способ увидеть, как работает деструктурирование, но он также кажется немного бессмысленным. Если бы вы видели это в вакууме, вы, вероятно, подумали бы, что это похоже на slice, но с большим количеством шагов. Я бы простил тебя за такие мысли.
Более сложный пример деструктуризации объекта
Для нас, разработчиков, большая часть сложностей может быть связана с тем, что произойдет в какой-то момент в будущем. Конечно, существует humble Promise, или асинхронный объект, который вернется в какой-то момент в будущем. Но вскоре мы переходим к наблюдаемым объектам и вводим временной элемент. Эти вещи меняются со временем, детка.
Сталкивались бы мы с этим каждый день как разработчики? Итак, если на вашем веб-сайте есть форма, в которую люди могут вводить данные и просматривать результаты, каковы наши источники событий? Пользователь нажимает на кнопку поиска; это верно. Но как насчет сортировки и фильтрации данных? Было бы неплохо сделать все это из одного наблюдаемого объекта и не помещать наш наполовину отсортированный массив во временную переменную, пока мы с ним работаем.
Это очень хорошее приложение для деструктурирования. Давайте разберемся, почему.
Давайте начнем с подписки на переменную SortOrder. Это имя заголовка, по которому будут сортироваться данные. В нашем удобном редакторе он сообщает нам тип данных, которые будут получены из этого наблюдаемого объекта, как это предлагается системой типов:
Неудивительно, что это наблюдаемая<строка>. Со временем она будет отображаться, когда кто-то нажмет на кнопку "Порядок сортировки". Но мы хотим объединить эту наблюдаемую строку с другими наблюдаемыми. Что происходит, когда мы это делаем?:
Ах, это не очень понятно. Теперь это наблюдаемая величина типа string, строка. TypeScript извлекает эту переменную из воздуха, поскольку она описывает форму наших данных.
Досадно, что нам пришлось бы получать доступ к значениям в этом файле с помощью индекса, поэтому нам пришлось бы помнить, когда мы назначали каждую переменную в операторе pipe. Для достаточно сложных цепочек pipe() вскоре мы будем выглядеть следующим образом:
Что действительно интересно, так это то, что при каждом последующем вызове combineLatestWith мы получаем наш исходный кортеж с новейшим значением combineLatestWith, добавленным в конце.
Это быстро превращается в хаос, поскольку наша информация о типе сокращается из-за огромной длины:
Наведение курсора на объект, чтобы увидеть его тип, показывает нам, с чем мы имеем дело:
Это не вызывает радости.
Если бы у нас была какая-то еще более сложная система, и нам пришлось бы учитывать множество наблюдаемых параметров, чтобы получить результат, тогда было бы не так сложно представить себе глубоко вложенный набор кортежей и объектов. И нам пришлось бы выбирать их по индексу.
Так что, по сути, просто окружите весь блок кода фразой "Никогда НЕ ПЕРЕДЕЛАЕТЕ ЭТО, ВАС УВОЛЯТ" и продолжайте жить своей жизнью.
К счастью, деструктурирование действительно может помочь в этом вопросе.
Давайте создадим простую HTML-таблицу с несколькими полями и сортировкой:
Мы используем формы, которые, по крайней мере, настолько сложны в повседневной жизни, но здесь происходит немало изменений. Мы хотим мгновенно реагировать, когда кто-то нажимает кнопку поиска и кнопку сортировки, но мы также хотим использовать последние значения из других полей, таких как поля для ввода имени или профессии.
К счастью, мы можем использовать операторы RxJS именно для достижения этой цели:
this.tableData$ = this.sortOrder.pipe( combineLatestWith(this.header), combineLatestWith(this.searchButton$), withLatestFrom(this.formGroup.valueChanges) )
Но после этого мы хотим использовать switchmap для завершения любых запросов в процессе выполнения, если будут поступать новые запросы. Какова подпись объекта, который передается в оператор switchMap?:
Для каждого нового используемого нами оператора RxJS он помещается в кортеж. Итак, представьте, что у нас есть источник данных (например, HTTP API), который имеет сигнатуру, подобную этой:
fakeAsyncronousDataSource(name: string, profession: string, header: Header | undefined, sortOrder: SortOrder, page: number)
В итоге вся наша цепочка выглядит примерно так:
this.tableData$ = this.sortOrder.pipe( combineLatestWith(this.header), combineLatestWith(this.searchButton$), withLatestFrom(this.formGroup.valueChanges), switchMap(x => { return this.fakeAsyncronousDataSource(x\[1].name ?? '', x[1].profession ?? '', x[0\][0]\[1], x[0\][0][0], 0) }), startWith(testData.slice(0, 10)) ) >
Представьте, что вы пытаетесь представить это с невозмутимым видом на обзоре кода.
- О, и это тот момент, когда я извлекаю значения из кортежа, расположенного на трех уровнях, и передаю их функции. Я делаю это, полностью используя индексные значения, которые я и только я знал, когда писал код".
А потом смех сменяется долгим пристальным взглядом, когда они понимают, что ты на самом деле говоришь серьезно. Нет, нет, давай не будем такими.
На помощь приходит деструктурирование объектов TypeScript
К счастью, мы можем "развернуть" многоуровневый кортеж, используя деструктурирование объектов TypeScript. Это довольно быстрый переход, но наша цепочка RxJS теперь выглядит следующим образом:
this.tableData$ = this.sortOrder.pipe( combineLatestWith(this.header), combineLatestWith(this.searchButton$), withLatestFrom(this.formGroup.valueChanges), switchMap(([[[sort, header], _], formData]) => { return this.fakeAsyncronousDataSource(formData.name ?? '', formData.profession ?? '', header, sort, 0) }), startWith(testData.slice(0, 10)) )
Это имеет много преимуществ.
Во-первых, и это наиболее очевидно, мы знаем, что такое переменные, когда они используются при вызове функции. Нам не нужно извлекать значения по индексу, что значительно улучшает читаемость.
Во-вторых, мы можем мысленно связать каждое значение кортежа при деструктуризации с соответствующими операторами конвейера перед ним. Итак, если мы хотим что-то добавить в конвейер, это не представляет большого неудобства.
Справедливости ради, обычно требуется довольно долгое изучение языковых возможностей, чтобы по-настоящему оценить их преимущества. Но в данном случае это довольно очевидно. Когда у вас есть сложный тип, который создается с помощью чего-то вроде наблюдаемой цепочки, не бойтесь прибегнуть к чему-то вроде деструктурирования.
Вывод
В этой статье мы узнали, как деструктурировать объекты в самых разных случаях. Мы увидели, как их можно использовать в массивах, а также как их можно использовать в наблюдаемой цепочке. Как бы мы их ни использовали, они помогают нам писать чистый, поддерживаемый код.
Не стесняйтесь клонировать образец на GitHub здесь. После того, как вы это сделаете и запустите проект, вы также сможете получить доступ к простому примеру на http://localhost:4200/simple и сложному примеру на http://localhost:4200/complex, соответственно.