Применяй экспертные паттерны, которые защищают реальную кодовую базу: имитация номинальности, проверка без расширения типа и внешние объявления.
Открыть этот урок в KodokonТипизация в TypeScript структурная: два типа с одинаковой формой взаимозаменяемы, поэтому string с идентификатором пользователя неотличим от любой другой string. Брендирование имитирует номинальную типизацию, пересекая базовый тип с фантомным свойством, ключ которого - это unique symbol: ни у одного реального значения его нет, но компилятор теперь различает бренды.
declare const brand: unique symbol;
type Brand<T, Name extends string> =
T & { readonly [brand]: Name };
type UserId = Brand<string, "UserId">;
type OrderId = Brand<string, "OrderId">;
function asUserId(raw: string): UserId {
return raw as UserId;
}
declare function loadUser(id: UserId): void;
loadUser(asUserId("u_42")); // OK
// loadUser("u_42"); // error: bare string rejectedОператор satisfies (TypeScript 4.9) закрывает конкретный пробел. Аннотация : T расширяет тип переменной до T и теряет выведенные литералы; as T отключает часть проверок. satisfies T проверяет, что выражение соответствует T, не трогая выведенный тип, который остаётся максимально точным. Это идеальный инструмент для конфигурационных объектов: полная проверка при сохранённой точности.
type RouteDef = { path: string; auth?: boolean };
const routes = {
home: { path: "/" },
admin: { path: "/admin", auth: true },
} satisfies Record<string, RouteDef>;
routes.admin.auth;
// literal type true: the inferred precision is
// preserved, and an unknown key would be rejectedФайл .d.ts содержит исключительно внешние объявления: он описывает значения, которые будут существовать во время выполнения, не порождая ни строчки JavaScript. Чтобы расширить глобальную область из модуля, блок declare global обязателен - и спецификация разрешает его только в файле, который действительно является модулем, отсюда идиома export {} в начале файла.
export {};
declare global {
interface Window {
analytics: { track(event: string): void };
}
}Для JavaScript-зависимости без опубликованных типов declare module создаёт внешний модуль: компилятор будет использовать твоё объявление при каждом импорте пакета. Следи за областью действия skipLibCheck: когда он включён, он игнорирует ошибки во всех файлах .d.ts, включая твои; поэтому проверяй свои объявления тестами типов, размещёнными в обычных файлах .ts.
declare module "legacy-lib" {
export interface InitOptions {
debug?: boolean;
}
export function init(options?: InitOptions): void;
}satisfies T и аннотацией : T?Brand<string, ...>?export {} в начало файла .d.ts, который использует declare global?