Глубокое погружение в рекурсивные псевдонимы типов 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 и найдите прекрасную работу