Глубокое погружение в рекурсивные псевдонимы типов TypeScript
До TypeScript 3.7 рекурсивная ссылка на тип приводила к тому, что компилятор TypeScript выдавал сообщение об ошибке циклических ссылок. Разработчики должны найти обходной путь (т. е. использовать интерфейс), чтобы получить рекурсивную ссылку.
Рекурсивный тип введен начиная с TypeScript 3.7. Это позволяет ссылаться на тип из его собственного определения, откладывая ссылку на тип до создания экземпляра.
Поскольку рекурсия является распространенным шаблоном программирования, существует множество случаев использования, в которых рекурсивный тип очень полезен. Используя рекурсивный тип, мы можем представить сложную структуру данных в сжатой форме.
В этой статье я расскажу, как использовать рекурсивные псевдонимы типов в TypeScript.
Используйте рекурсивный тип для представления структуры данных
Рекурсивный тип может использоваться для представления типа данных, имеющего вложенную структуру. Ниже приведен пример:
const myData = { top: 1, rest: { top: 2, rest: { top: 3, rest: null } } };
Этот тип данных можно абстрагировать как тип данных стек.
type Stack<T> = { top: T; rest: Stack<T> | null; } ; // Then we can strong typed the data const myData: Stack<number> = { top: 1, rest: { top: 2, rest: { top: 3, rest: null } } };
Приведенный выше код определяет общий тип данных Stack
. Он состоит из свойства top
типа T
и свойства rest
того же типа Stack
внутри определения типа.
Еще один хороший пример — тип данных JSON. В примере игровой площадки рекурсивного типа TypeScript для определения типа JSON используется следующий фрагмент кода:
type Json = string | number | boolean | null | Json[] | { [key: string]: Json }; const exampleStatusJSON: Json = { available: true, username: "Jean-loup", room: { name: "Highcrest", // Cannot add functions into the Json type // update: () => {} }, };
Тип Json
работает так же, как наш предыдущий тип Stack
, и мы используем псевдоним типа Json
для представления вложенных дочерних узлов JSON.
Как показано в приведенных выше примерах, использование рекурсивного типа делает определение типа более понятным и читабельным, поскольку определение типа соответствует рекурсивной природе структуры данных.
Ограничение рекурсивного типа
Псевдонимы рекурсивного типа в TypeScript имеют ограничение, заключающееся в том, что они не допускают немедленного «самостоятельного создания». Ниже приведен пример:
type Stack<T> = { top: T; rest: Stack<T> | null; } ; type Stack1 = Stack<Stack1>;
В этом случае компилятор TypeScript выдает ошибку: «Псевдоним типа «Stack1» циклически ссылается на себя. (2456)». Ограничение разумно, так как немедленная ссылка на себя в приведенном выше примере вызовет бесконечную рекурсию во время компиляции.
Расширенное использование
Комбинируя псевдонимы рекурсивного типа с другими расширенными функциями типов (например, условным типом), мы можем выполнять некоторые сложные операции с типами.
Допустим, у нас есть тип Client
, представляющий данные клиента.
type Client = { id: number, name: string, address: { id: number, suburb: { postCode: number } } };
Наша цель — определить PropertyType
, который может извлекать тип свойства из структуры вложенного типа.
type postCode = PropertyType<Client, 'address.suburb.postCode'>; // the postCode type returns "number" type noExist = PropertyType<Client, 'address.suburb.noExist'>; // return "never" because the path does not exist
Тип должен иметь возможность принимать два аргумента: аргумент универсального типа и строку пути к свойству для поиска свойства. Если путь к свойству не существует, верните типnever
.
Псевдонимы рекурсивных типов хорошо подходят для этой ситуации, поскольку они позволяют нам перемещаться по структуре типов объектов.
Ниже представлена реализация PropertyType
type PropertyType<T, Path extends string> = Path extends keyof T ? T[Path] : Path extends `${infer K}.${infer R}` ? K extends keyof T ? PropertyType<T[K], R> : never : never;
Хотя этот тип является однострочным, он использует несколько расширенных функций поверх рекурсивных псевдонимов типов:
- Дженерики
- Инфер
- Условные типы
- ключ оператора
Первая часть условного типа проверяет, является ли путь ключом типа T, если да, то тип устанавливается равным значению ключа: T[Path]
.
Path extends keyof T ? T[Path]
Затем во второй части проверки условного типа используется оператор infer
для извлечения K
и R
. Здесь шаблон ${...}.${...}
используется для сопоставления строки с .
exists.
Path extends `${infer K}.${infer R}` ? K extends keyof T ? PropertyType<T[K], R>
Когда все условия совпадают, мы делаем рекурсивный вызов свойства следующего уровня PropertyType<T[K], R>
. В противном случае это означает, что путь не существует, и будет возвращен тип never
.
Краткое содержание
В этой статье мы рассмотрим несколько примеров псевдонимов рекурсивных типов. Псевдоним рекурсивного типа может представлять структуру данных с рекурсивной природой, и мы также можем использовать его для обхода или управления сложными типами данных.
Я надеюсь, что эта статья может быть вам полезна. Удачного программирования!
Повышение уровня кодирования
Спасибо, что являетесь частью нашего сообщества! Перед тем, как ты уйдешь:
- 👏 Хлопайте за историю и подписывайтесь на автора 👉
- 📰 Смотрите больше контента в публикации Level Up Coding
- 💰 Бесплатный курс собеседования по программированию ⇒ Просмотреть курс
- 🔔 Подписывайтесь на нас: Twitter | ЛинкедИн | "Новостная рассылка"
🚀👉 Присоединяйтесь к коллективу талантов Level Up и найдите прекрасную работу