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
#typescript #ts @haradkou_sdet
В этом посте были ссылки, но мы их удалили по правилам Сетки`````