Привет, с вами снова @devmargooo и сегодня мы с вами поговорим о типизации в приложении на typescript. Структурная типизация в typescript, в отличие от номинативной, определяет совместимость типов, основываясь не на названии типа, а на его наборе полей. В этом структурная типизация похожа на утиную: если это выглядит как утка, плавает как утка и крякает как утка, то, вероятно, это утка. К сожалению, это может приводить к проблемам. Следующий код не приведет к ошибке компиляции, несмотря на то, что тип
some_string указан как string, а не Email :type Email = string;
function auth(login:Email) {}
const some_string:string = "some_string";
auth(some_string); // ok
Тип
string структурно идентичен типу Email , поэтому ошибки нет. Номинативные типы работают по-другому - они совместимы только при явном использовании. К сожалению, встроенной поддержки таких типов в typescript нет, но есть хак, который называют брендированием. Его суть заключается в том, что мы добавляем к типу уникальное свойство:
type Email = string & { _: 'Email' };Теперь тип
Email будет структурно не совместим с типом string. Мы получим ошибку компиляции:type Email = string & { _: 'Email' };
function auth(login:Email) {}
const some_string:string = "some_string";
auth(some_string); // Argument of type 'string' is not assignable to parameter of type 'Email'.Чтобы код заработал, нам придется явно указывать, что переменная имеет тип
Email:const some_string = "some_string" as Email;
Или добавить проверку в наш код, которая также будет выводить нужный нам тип:
function isEmail(value:string): value is Email {
const emailRegex = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
return emailRegex.test(value);
}Для удобства создания брендированных типов можно воспользоваться дежнериком:
type Brand<B extends string, T> = T & { readonly _: B }Пример использования:
type Email = Brand<'Email', string>;