Typescript рекурсивный тип

Который точно не оставит вас равнодушным. Давно здесь не было зубодробительного контента. Плюс наткнулся на рекурсивный тип от поста тара с типами(я говорил, что сам читаю людей из папки?)

Давайте спроектируем такой тип, который бы использовался для логгирования. Не простого, который принимает строки, а такого, который умеет работать с числами в printf нотации [прим. printf - print + formatting. Т.е. красивый вывод]. Для тех, кто не знаком с printf нотации я поясню. Возьмем функцию console.log из браузераноды и напишем следующее `console.log( '%s:%d Okay!', 'foo', 4 )

Выведет нам следующее

`"foo:4 Okay!"

Идея довольно простая, заменить "процент + что-то" на значение из следующего параметра. Как правило поддерживается следующие значения. Этот список намеренно сокращен. Ознакомится с примером из node.js можно из документации по util.format

  • %s - строки
  • %d - числа(любые)
  • %i - целые числа (integer)
  • %f - дробные числа
  • %o - Объект
  • %j - JSON строка

Итак. Задача становится более понятная: написать такую функцию, которая принимает строку и если есть "магическая" последовательность - указать через автодополнение, что не хватает переданных аргементов. Например данное использование должно вывести ошибку log(this is an error %s`) // ошибка, т.к. есть "магическая" последовательность. Ожидается строка как 2 агрумент

Тут нам поможет рекурсивный тип с infer. Но для начала определим эти символы в виде объекта

`type ControlsMap = { 's': string; 'd': number; 'f': number; 'j': unknown; 'o': object; 'O': object; }

Далее нам нужно в строке найти последовательность. Алгоритм может быть следующим:

0. строка(как Generic) соответсвует %<новый тип><остальная строка> ? 1. Если да = <новый тип> является кем-то из ключей в ControlsMap (т.е. s, d, f и т.д.)? 1.1. Если да, то сохраняем в массив ControlsMap[<новый тип>] и вызываем тотже алгоритм, но уже не для всей строки, а для <остальная строка> 1.2. Если нет, то вызываем тотже алгоритм, но уже не для всей строки, а для <остальная строка> (т.е. не сохраняем новый аргумент, т.к. нет соответсвия по ключам ControlsMap) 2. Если нет, то возвращаем пустой массив аргументов, т.к. нету "магической" последовательности

Преобразуя алгоритм в код получим следующее

`type ParsePrintFormat< S extends string, A extends readonly unknown[] = readonly [],

= S extends ${infer _}%${infer ControlLike}${infer Last} // 0 ? ControlLike extends keyof ControlsMap // 1 ? ParsePrintFormat< // 1.1 Last, readonly [...A, ControlsMap[ControlLike]]

: ParsePrintFormat // 1.2 : A // 2

Полный код доступен на typescript playground

https://www.typescriptlang.org/play/?#code/C4TwDgpgBAwg9gO2AJzgGwM4FkCGYoC8UA3gFBQVQDkGVAXFBigJYIDmA3OZVQCb1QEAVwC2AIwjIulagDMBw8ZOk8AVgKEIA1gjgB3BCopU4AuGNUQAxsCPUA8mYvXbpAL6lSoSFAAKOZAwIX2RWYAAxOGQRHGAAHm4KAGUoCAAPYAgEXgxGFnYAGkSoAEFUjKycqGQIHF5ENBAoTR19BABtAF1Catr6hEaoLqKAPh6U9Mzs3IADABJiVllJKAB9NwBSBaWV+CRUNAAZZi0IN22EZeQoQ5wmNxnigH5YRBR0Y9PyqarTkDhZK99uhsHhnn4AkEQmFItFYgkZDJbkwiojKDU6g0mu0AHR4koFIHvTC4MDtPbEz4QTqdYojYoMfyBYKhJCwmLxZHAQklekyBglTyyTQ2ZiIKBoOBsBGUKyIJhQLAQDAYHBsaCTSq5JistiE4pyhAKkrINi5TXTCHM6FsqIcuJKlVqiB8ygjAAUxRkImVqvVDEdfog+rRFDxOICZoFpowxQAlCQDfL0BAcZK2O6fU71YTw5GMHH3EA

#typescript #ts @haradkou_sdet


В этом посте были ссылки, но мы их удалили по правилам Сетки`````