Pull to refresh

TypeScript тип any против всех

Reading time3 min
Views8.5K

В жизни каждого разработчика на 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 перестанет нам помогать в борьбе за качественное ПО.

Tags:
Hubs:
Total votes 5: ↑4 and ↓1+4
Comments10

Articles