Typescript объект вложенный объект

Назначение типов вложенным объектам в TypeScript

Назначение типов вложенным объектам в TypeScript

Объекты — это то, с чем вы имеете дело, работая разработчиком JavaScript, и, разумеется, это справедливо и для TypeScript. TypeScript предоставляет вам несколько способов определения определений типов для свойств объектов. В этой статье мы рассмотрим несколько из них, начиная с простых примеров и переходя к некоторым расширенным определениям типов.

Использование ключевого слова type

Свойствам объекта можно присвоить определения типа с помощью ключевого слова type в TypeScript. Это самый простой и предпочтительный метод присвоения определений типов при работе с простыми объектами. Вот пример типа Airplane и объекта airplane .

// Defining Airplane Type type Airplane =  model: string; flightNumber: string; timeOfDeparture: Date; timeOfArrival: Date; >; // Creating airplane Object const airplane: Airplane =  model: "Airbus A380", flightNumber: "A2201", timeOfDeparture: new Date(), timeOfArrival: new Date(), >; 

Вложенные объекты

Если ваш объект имеет вложенный объект, вы можете вложить определения типов, используя само ключевое слово type. Вот пример вложенного объекта с именем caterer внутри определения типа Airplane .

type Airplane =  model: string; flightNumber: string; timeOfDeparture: Date; timeOfArrival: Date; caterer:  name: string; address: string; phone: number; >; >; const airplane: Airplane =  model: "Airbus A380", flightNumber: "A2201", timeOfDeparture: new Date(), timeOfArrival: new Date(), caterer:  name: "Special Food Ltd", address: "484, Some Street, New York", phone: 1452125, >, >; 

Абстрагирование вложенных объектов в отдельные типы

Если у вас есть большие объекты, определение вложенных типов может стать громоздким. В таком случае вы можете определить отдельный тип Caterer для вложенного объекта. Это также абстрагирует тип Caterer от типа Airplane , что позволит вам использовать тип Caterer в других частях вашего кода.

type Airplane =  model: string; flightNumber: string; timeOfDeparture: Date; timeOfArrival: Date; caterer: Caterer; >; const airplane: Airplane =  model: "Airbus A380", flightNumber: "A2201", timeOfDeparture: new Date(), timeOfArrival: new Date(), caterer:  name: "Special Food Ltd", address: "484, Some Street, New York", phone: 1452125, >, >; 

Использование сигнатур индекса с вложенными объектами

Сигнатуры индекса можно использовать, когда вы не уверены, сколько свойств будет у объекта, но уверены в типе свойств объекта. Мы можем определить другой тип, называемый Seat , который может быть подробной информацией о пассажире, путешествующем на каждом месте типа Airplane . Мы можем использовать сигнатуру индекса, чтобы присвоить строковый тип всем свойствам мест.

type Caterer =  name: string; address: string; phone: number; >; type Seat =  [key: string]: string; >; type Airplane =  model: string; flightNumber: string; timeOfDeparture: Date; timeOfArrival: Date; caterer:  name: string; address: string; phone: number; >; seats: Seat[]; >; const airplane: Airplane =  model: "Airbus A380", flightNumber: "A2201", timeOfDeparture: new Date(), timeOfArrival: new Date(), caterer:  name: "Special Food Ltd", address: "484, Some Street, New York", phone: 1452125, >, seats: [  name: "Mark Allen", number: "A3", >,  name: "John Doe", number: "B5", >, ], >; 

Интерфейсы для назначения типов свойствам объекта

Если вам нужно создать классы для генерации объектов, лучше использовать interfaces вместо ключевого слова type. Вот пример объекта airplane , созданного с использованием класса Airplane , который расширяет интерфейс IAirplane .

interface IAirplane  model: string; flightNumber: string; timeOfDeparture: Date; timeOfArrival: Date; > class Airplane implements IAirplane  public model = "Airbus A380"; public flightNumber = "A2201"; public timeOfArrival = new Date(); public timeOfDeparture = new Date(); > const airplane: Airplane = new Airplane(); 

Вложенные объекты с использованием интерфейсов

Точно так же, как мы использовали ключевое слово type , interfaces также можно использовать для строгой типизации вложенных объектов с классами.

interface ICaterer  name: string; address: string; phone: number; > interface ISeat  name: string; number: string; > interface IAirplane  model: string; flightNumber: string; timeOfDeparture: Date; timeOfArrival: Date; > class Caterer implements ICaterer  public name = "Special Food Ltd"; public address = "484, Some Street, New York"; public phone = 1452125; > class Seat implements ISeat  public name = "John Doe"; public number = "A3"; > class Airplane implements IAirplane  public model = "Airbus A380"; public flightNumber = "A2201"; public timeOfArrival = new Date(); public timeOfDeparture = new Date(); public caterer = new Caterer(); public seats = [new Seat()]; > const airplane: Airplane = new Airplane(); 

Что вы можете сделать дальше 🙏😊

Если вам понравилась статья, рассмотрите возможность подписки на Cloudaffle, мой канал на YouTube, где я продолжаю публиковать подробные руководства и все обучающие материалы для программного обеспечения. Разработчики. Вы также можете следить за мной на Hashnode; дескриптор моего профиля — @Cloudaffle. Ставьте лайк, если вам понравилась статья; это поддерживает мою мотивацию на высоком уровне 👍.

. информация Также опубликовано здесь.

Источник

Typescript: Объединение типов в глубину

Пошаговое руководство о том, как в TypeScript написать такой generic-тип, который объединяет произвольные вложенные key-value структуры.

Примечание переводчика: я намерено не стал переводить некоторые слова (вроде generic, key-value), т.к., на мой взгляд, это только усложнит понимание материала.

TLDR:

Исходный код для DeepMergeTwoTypes будет в конце статьи. Скопируйте его в вашу IDE, чтобы поиграть с ним.

Как это выглядит в vsCode:

Если вы не уверены в своих познаниях о том, как работают generic-и в TypeScript, вы можете ознакомиться с этой статьёй (Miniminalist Typescript — Generics)

Если вы хотите проверить корректность кода просто скопируйте его в вашу IDE (прим. переводчика: или в TypeScript Playground песочницу).

Disclaimer

Используя код из этой статьи в production вы делаете это на свой страх и риск (тем не менее, мы его используем).

Проблема поведения &-оператора в Typescript

Для начала посмотрим на проблему объединения типов. Определим два типа A и B и новый тип C , который является результатом объединения A & B

type A = < key1: string, key2: string >type B = < key2: string, key3: string >type C = A & B const a = (c: C) => c.

Всё выглядит замечательно до тех пор, пока вы не начнёте объединять несовместимые типы данных.

type A = < key1: string, key2: string >type B = < key2: null, key3: string >type C = A & B

Тип A определяет key2 как строку, в то время как в типе B это null .

Typescript выводит это объединение несовместимых типов как never и тип C просто перестаёт работать. В то время как мы ожидали чего-то вроде этого:

Пошаговое решение

Давайте начнём с создания generic-типа, который будет рекурсивно объединять типы Typescript. Для начала мы определим 2 вспомогательных generic-типа.

GetObjDifferentKeys<>

type GetObjDifferentKeys = Omit & Omit

Этот тип принимает на входе 2 объекта и возвращает новый объект, содержащий только уникальные ключи из A и B .

type A = < key1: string, key2: string >type B = < key2: null, key3: string >type C = GetObjDifferentKeys['']

GetObjSameKeys<>

В противовес предыдущему generic-у объявим другой тип, который вытащит все ключи, которые есть в обоих объектах.

type GetObjSameKeys = Omit
type A = < key1: string, key2: string >type B = < key2: null, key3: string >type C = GetObjSameKeys

Все вспомогательные типы готовы, так что мы можем приступать к реализации нашего главного generic-типа DeepMergeTwoTypes

type DeepMergeTwoTypes = // "не общие" (уникальные) ключи - опциональны Partial // общие ключи - обязательны & < [K in keyof GetObjSameKeys]: T[K] | U[K] >

Этот generic находит все «не общие» ключи между объектами T и U , и сделает их опциональными (необязательными). Спасибо за это стандартному типу Partial<> , из стандартной библиотеки типов Typescript. Этот тип с опциональными ключами объединяется (посредством & -оператора) с объектом содержащим все общие ключи между T и U , значением которых будут T[K] | U[K] .

Посмотрите на пример ниже. Новый generic нашёл «не-общие» ключи и сделал их опциональными ( ? ), в то время как остальные ключи строго обязательны.

type A = < key1: string, key2: string >type B = < key2: null, key3: string >const fn = (c: DeepMergeTwoTypes) => c.

Но наш DeepMergeTwoTypes generic не работает рекурсивно со вложенными структурами. Так что давайте вынесем объединение объектов в новый generic тип MergeTwoObjects и будем вызывать DeepMergeTwoTypes рекурсивно до тех пор, пока он не объединит все вложенные структуры.

// этот generic рекурсивно вызывает DeepMergeTwoTypes<> type MergeTwoObjects = // "не общие" (уникальные) ключи - опциональны Partial // общие ключи - обязательны & <[K in keyof GetObjSameKeys]: DeepMergeTwoTypes> export type DeepMergeTwoTypes = // проверяем являются ли типы массивами, распаковываем и запускаем рекурсию [T, U] extends [< Typescript объект вложенный объект: unknown >, < Typescript объект вложенный объект: unknown >] ? MergeTwoObjects : T | U

PRO TIP: Обратите внимание на то, что в DeepMergeTwoTypes используется if-else условие ( extends ?: ) Мы проверяем что и T и U удовлетворяют условию, засунув их в кортеж (tuple) [T, U] . Это поведение похоже на && -оператор в Javascript.

Этот generic проверяет, что оба параметра соответствуют типу < Typescript объект вложенный объект: unknown >(это Object ). Если это так, то он объединяет их посредством MergeTwoObject<> . Этот процесс рекурсивно повторяется для всех вложенных объектов.

Примечание переводчика: Проверка на extends < Typescript объект вложенный объект: unknown > позволяет отфильтровать все не-объекты, т.е. строки, числа, booleans и т.д..

И вуаля! Теперь наш generic рекурсивно применён ко всем вложенным объектам. Пример:

type A = < key: < a: null, c: string>> type B = < key: < a: string, b: string>> const fn = (c: MergeTwoObjects) => c.key.

Увы, нет. Наш новый generic не поддерживает массивы.

Прежде, чем мы продолжим, мы должны понять ключевое слово infer (to infer — выводить).

infer смотрит на структуру данных и вытаскивает её тип (в нашем случае это массив). Подробнее почитать про infer можно здесь (Type inference in conditional types).

Пример использования infer . Здесь мы получаем тип отдельно взятого элемента массива ( Item ):

Теперь мы можем добавить поддержку массивов, просто добавив эти две строки, в которых мы выводим тип значений элементов массива. И рекурсивно вызываем DeepMergeTwoTypes для содержимого массивов.

export type DeepMergeTwoTypes = // ----- 2 добавленные строки ------ // эта ⏬ [T, U] extends [(infer TItem)[], (infer UItem)[]] // . и эта ⏬ ? DeepMergeTwoTypes[] : . rest of previous generic . 

Сейчас DeepMergeTwoTypes может рекурсивно вызывать сам себя, в случае если значения это объекты или массивы.

type A = [< key1: string, key2: string >] type B = [< key2: null, key3: string >] const fn = (c: DeepMergeTwoTypes) => c[0].

И это работает! На этом всё?

Эх. Нет. Последняя проблема заключается в объединении Nullable типов с non-nullable .

type A = < key1: string >type B = < key1: undefined >type C = DeepMergeTwoTypes['key']

Ожидаемый тип — string | undefined , но на деле это не так. Давайте добавим ещё две строки в нашу цепочку if-else .

export type DeepMergeTwoTypes = [T, U] extends [(infer TItem)[], (infer UItem)[]] ? DeepMergeTwoTypes[] : [T, U] extends [< Typescript объект вложенный объект: unknown>, < Typescript объект вложенный объект: unknown >] ? MergeTwoObjects // ----- 2 добавленные строки ------ // эта ⏬ : [T, U] extends [ < Typescript объект вложенный объект: unknown >| undefined, < Typescript объект вложенный объект: unknown >| undefined ] // . и эта ⏬ ? MergeTwoObjects, NonNullable> | undefined : T | U

Проверяем объединение nullable значений:

type A = < key1: string >type B = < key1: undefined >const fn = (c: DeepMergeTwoTypes) => c.key1;

И. Вот теперь всё!

Мы сделали это! Значения корректно объединяются даже для nullable , вложенных объектов и массивов.

Давайте опробуем наш generic на более сложных данных:

type A = < key1: < a: < b: 'c'>>, key2: undefined > type B = < key1: < a: <>>, key3: string > const fn = (c: DeepMergeTwoTypes) => c.

/** * Принимает 2 объекта T и U и создаёт новый объект, с их уникальными * ключами. Используется в `DeepMergeTwoTypes` */ type GetObjDifferentKeys = Omit & Omit /** * Принимает 2 объекта T and U и создаёт новый объект с их ключами * Используется в `DeepMergeTwoTypes` */ type GetObjSameKeys = Omit type MergeTwoObjects = // "не общие" ключи опциональны Partial // общие ключи рекурсивно заполняются за счёт `DeepMergeTwoTypes` & < [K in keyof GetObjSameKeys]: DeepMergeTwoTypes > // объединяет 2 типа export type DeepMergeTwoTypes = // проверяет являются ли типы массивами, распаковывает их и // запускает рекурсию [T, U] extends [(infer TItem)[], (infer UItem)[]] ? DeepMergeTwoTypes[] // если типы это объекты : [T, U] extends [ < Typescript объект вложенный объект: unknown>, < Typescript объект вложенный объект: unknown >] ? MergeTwoObjects : [T, U] extends [ < Typescript объект вложенный объект: unknown >| undefined, < Typescript объект вложенный объект: unknown >| undefined ] ? MergeTwoObjects, NonNullable> | undefined : T | U // тестируем: type A = < key1: < a: < b: 'c'>>, key2: undefined > type B = < key1: < a: <>>, key3: string > const fn = (c: DeepMergeTwoTypes) => c.key

Последний штрих

Как бы так поправить DeepMergeTwoTypes generic, чтобы он мог принимать N аргументов вместо двух?

Я оставлю этот материал для следующей статьи, но вы можете посмотреть мой рабочий черновик здесь).

Примечание переводчика

Это мой первый опыт перевода. Убедительная просьба об опечатках, запятых и просто косноязычных фразах писать в личку.

Источник

Читайте также:  Python classes super init
Оцените статью