Проблема смешивания идентификаторов
В типичном проекте на TypeScript мы часто сталкиваемся с ситуацией, когда разные сущности имеют одинаковые типы данных, например строки (string). Это может привести к ошибкам при передаче аргументов функций или методов, так как компилятор не видит разницы между такими значениями.
Например:
type UserId = string;
type OrderId = string;
async function getOrderDetails(userId: UserId, orderId: OrderId) {
return db.orders.findFirst({
where: { id: userId },
});
}
Здесь легко случайно передать orderId, где ожидается userId. Компилятор не заметит ошибки, поскольку оба типа совпадают.
Что такое брендированные типы?
Брендированный тип позволяет добавить дополнительную информацию о типе на уровне системы типов TypeScript, не влияя на выполнение программы во время работы. Для этого используется пересечение примитивного типа и объекта с «призраковой» меткой (phantom type).
Пример реализации брендированного типа:
type Brand<T, B extends string> = T & { readonly _brand: B };
type UserId = Brand<string, 'UserId'>;
type OrderId = Brand<string, 'OrderId'>;
Теперь переменные этих типов нельзя использовать взаимозаменяемо, даже если они основаны на одном базовом типе (string):
const userId: UserId = "abc" as UserId;
const orderId: OrderId = "def" as OrderId;
getOrderDetails(userId, orderId);
Если попытаться поменять аргументы местами, компилятор выдаст ошибку.
Преимущества использования брендированных типов
Использование брендированных типов помогает избежать ошибок еще до запуска приложения, что улучшает качество кода и снижает вероятность появления багов в продакшен-среде. Кроме того, это решение не добавляет накладных расходов на этапе выполнения программы.