Интересная техника уточнения примитивных типов в TypeScript
В книге Эффективный TypeScript
- Дэна Вандеркама, прочитал одну интересную технику для более строгой проверки примитивных типов данных. Которой хочу поделиться с вами.
В TS как и в JS, есть примитивные типы данных. Эти типы не несут в себе никакой дополнительной мета информации, но иногда хочется ее иметь в самом типе.
К примеру:
Copyconst path: string = "/var/www/"; funcWorkOnlyWithAbs(path);
Человек, который будет читать данный код, без труда поймет, что путь абсолютный. Но программа этого не понимает. И если другой программист запишет в переменную путь без слеша в начале, то программа сломается. Но typescript не покажет ошибки.
Мы можем ввести дополнительный метод:
Copyfunction isAbsolute(path: string): boolean { return path.startsWith('/') }
И использовать его в своем коде
Copyconst path: string = "/var/www/"; if (isAbsolute(path)) { funcWorkOnlyWithAbs(path); }
Однако, переменная path
все еще не типизирована, как абсолютный путь. В ней нет этой метаинформации, которая бы позволила ее однозначно идентифицировать.
Можно бы было ввести, что-то типа:
Copyinterface Path { path: string; } interface AbsolutePath extends Path { isAbsolute: true; } const path: AbsolutePath = { path: "/var/www/", isAbsolute: true } if (path.isAbsolute) { funcWorkOnlyWithAbs(path.path); }
Но первое - это неудобно, приходится везде таскать лишнюю абстракцию. Второе - это уже не string
.
Так вот техника заключается а том, что мы можем создать номинальный тип данных, расширяя string. TS точно будет знать, что эта переменная не просто строка, а имеет какие-то дополнительные уточнения и мета информацию.
Copytype AbsolutePath = string & {_brand: 'absolute'}; function isAbsolute(path: string): str is AbsolutePath { return path.startsWith('/') } if (isAbsolute(path)) { funcWorkOnlyWithAbs(path); }
В чем разница? В функции funcWorkOnlyWithAbs
. До этого она имела такую сигнатуру:
Copyfunction funcWorkOnlyWithAbs(path: string): void;
И ничего не мешало, вызвать ее так:
CopyfuncWorkOnlyWithAbs('./core/');
Если же мы сделаем ее параметр более уточненным, то такой вызов без проверки, сделать будет нельзя.
Copyconst path: string = "/var/www/"; function funcWorkOnlyWithAbs(path: AbsolutePath): void; if (isAbsolute(path)) { // path - точно содержит нужный нам путь funcWorkOnlyWithAbs(path); } funcWorkOnlyWithAbs('./core/'); // Ошибка
Техника может быть применена не только со строками:
Copytype Meters = number & {_type: 'meter'}; type Miles = number & {_type: 'mile'}; const meters = (v: number): Meters => v; const miles = (v: number): Miles => v; function calculateDistance(a: Meters, b: Meters): Meters { return Math.abs(a - b); } const a = meters(100); const b = meters(10); console.log(calculateDistance(a, b));
Вы точно знаете какую единицу измерения возвращает calculateDistance
, какую ждет в качестве аргументов. Это не нужно дополнительно обозначать в JSDoc
.
Причем для JS не появилось лишних абстракций. Обычные числа. А ts
стал строже и не позволит считать мили, там где требуются метры.
Вот такая интересная техника. Всем добра!