В жизни каждого разработчика на TypeScript наступает момент, когда ему хочется рвать все связи с типом any. А ведь по началу any казался таким милым! Сделай переменной аннотацию типа any и используй любое свойство и метод этой переменной так, как привык работать в JavaScript. Никаких тебе ошибок, все чинно и спокойно, по-старому.
Документация TypeScript оправдывает использование any только на время переноса кодовой базы из JavaScript в TypeScript, но считает постыдным его использование в полноценном проекте. Казалось бы, все хорошо, только в описании типов библиотечных функций самого TypeScript аннотации any встречаются. Очень полезный `JSON.parse`, один из таких примеров.
// из lib.es5.d.ts interface JSON { parse(text: string, reviver?: (this: any, key: string, value: any) => any): any; stringify(value: any, replacer?: (this: any, key: string, value: any) => any, space?: string | number): string; stringify(value: any, replacer?: (number | string)[] | null, space?: string | number): string; }
Выйти из положения позволяет умение TypeScript объединять описания интерфейсов. Давайте разберемся как это можно исправить библиотечное описание интерфейса JSON.
Использование слияния интерфейсов и перегрузки методов
interface JSON { parse( text: string, reviver?: (this: unknown, key: string, value: unknown) => unknown, ): unknown; }
Слияние деклараций интерфейсов
Когда TypeScript компилятор встречает декларацию интерфейса с одним и тем же именем несколько раз, он не расстраивается, а использует все описания. TypeScript как бы логически складывает все, что известно об интерфейсе, в единое описание и в дальнейшем исходит из этого.
В простейшем случае описания просто добавляются. Следующий пример показывает счастливый TypeScript.
interface User { name: string; } interface User { memberSince: number; } const user: User = { memberSince: 2022, name: 'author' }
В первом варианте описания интерфейс User содержит одно поле `name`, во втором варианте тоже одно поле, но с другим именем. TypeScript объединил оба описания в одно. Все ожидаемо.
Во втором варианте описания интерфейса можно повторить описание поля из первого. Но если так, то повторение должно быть полным. Совпадать должны и имена и типы полей.
interface User { name: string; memberSince: number; } interface User { memberSince: number; } const user: User = { memberSince: 2022, name: 'author' }
Если бы имена полей были одинаковыми, но разных вариантах поля имели разные типы, то TypeScript высказал бы свое недовольство кодом ошибки (2717). Как это видно в следующей песочнице.
interface User { name: string; memberSince: string;// пока еще все хорошо } interface User { memberSince: number;//ошибка } const user: User = { memberSince: 2022, // ошибка name: 'author' }
Как работает перегрузка функций в TypeScript
В javascript нет перегруженных функций, а в TypeScript есть. На самом деле в TypeScript в наличии только декларация перегрузки функций. Чтобы создать видимость перегруженных функций автору нужно создать все перегруженные декларации и одну реализацию, где вручную исследовать полученные аргументы.
function getYear():number; function getYear(ticks: number): number; function getYear(iso:string):number; //скрытая от вызывающей стороны реализация function getYear(arg?: undefined| string|number):number{ if(typeof arg === 'undefined'){ return 2022; } return new Date(arg).getFullYear(); }
Перегрузка методов интерфейса.
Декларация интерфейсов не подразумевает реализацию. При описании перегруженных методов интерфейса требуется лишь определить их сигнатуры.
interface User{ login(token:string):void; login(name: string, password: string):void; login():void; } declare const user:User; //вариант 1 user.login("--secret--"); //вариант 2 user.login("admin","pa$5w||d"); //вариант 3 user.login();
Мы обратим дополнительное внимание на то, что имена методов повторяются. Более того, при слиянии интерфейсов у TypeScript нет требования уникальности имени метода.
Этим мы и воспользуемся для решения вопроса с улучшенной типизацией метода parse интерфейса JSON
Слияние с библиотечным интерфейсом
Чтобы понять, как работает следующий отрывок, мы вспомним, что при слиянии интерфейсов более поздние описания перегруженных функций используются раньше, чем оригинальные. А поскольку библиотечное описание встретится компилятору раньше любого нашего, то наше определение будет иметь приоритет. В чем вы можете убедиться в песочнице
interface JSON { parse( text: string, reviver?: (this: unknown, key: string, value: unknown) => unknown, ): unknown; } const x = JSON.parse("0", function ():unknown { console.log(this);// TypeScript подсказывает, что this: unknown return 42; }); console.log(x) // TypeScript подсказывает, что x:unknown
Теперь мы можем не опасаться что TypeScript перестанет нам помогать в борьбе за качественное ПО.
ссылка на оригинал статьи https://habr.com/ru/post/653779/
Добавить комментарий