TypeScript Advanced Patterns: от Junior до Senior
Продвинутые техники TypeScript для написания type-safe кода
TypeScript стал стандартом для серьезной фронтенд разработки. 78% разработчиков предпочитают TypeScript чистому JavaScript. Но между базовым использованием и мастерством — пропасть. Зачем продвинутый TypeScript Меньше багов — до 15% багов отлавливаются на этапе компиляции Лучший DX — автокомплит и рефакторинг работают идеально Самодокументирование — типы как документация Рефакторинг без страха — компилятор найдет все проблемы Продвинутые типы 1. Conditional Types type IsString = T extends string ? true : false; type A = IsString ; // true type B = IsString ; // false // Практический пример type Flatten = T extends Array ? U : T; type Str = Flatten ; // string type Num = Flatten ; // number 2. Mapped Types type Readonly = { readonly [P in keyof T]: T[P]; }; type Partial = { [P in keyof T]?: T[P]; }; // Практический пример: API response types type APIResponse = { data: T; loading: boolean; error: string | null; }; type UserResponse = APIResponse ; 3. Template Literal Types type EventName = "click" | "focus" | "blur"; type Handler = `on${Capitalize }`; // "onClick" | "onFocus" | "onBlur" // Практический пример: type-safe routes type Route = "/users" | "/posts" | "/comments"; type API = `${Route}/:id`; // "/users/:id" | "/posts/:id" | "/comments/:id" 4. Discriminated Unions type Success = { status: "success"; data: T; }; type Error = { status: "error"; error: string; }; type Result = Success | Error; function handleResult (result: Result ) { if (result.status === "success") { // TypeScript знает, что здесь есть data console.log(result.data); } else { // TypeScript знает, что здесь есть error console.log(result.error); } } Utility Types: мастер-класс Pick и Omit interface User { id: string; name: string; email: string; password: string; } type PublicUser = Omit ; type LoginCredentials = Pick ; ReturnType и Parameters function fetchUser(id: string) { return { id, name: "John", email: "john@example.com" }; } type User = ReturnType ; type FetchUserParams = Parameters ; // [id: string] Awaited async function fetchData() { return { data: "hello" }; } type Response = Awaited >; // { data: string } Type Guards Пользовательские Type Guards function isString(value: unknown): value is string { return typeof value === "string"; } function processValue(value: unknown) { if (isString(value)) { // TypeScript знает, что value это string return value.toUpperCase(); } } Assert Functions function assertIsDefined ( value: T, message?: string ): asserts value is NonNullable { if (value === null || value === undefined) { throw new Error(message || "Value must be defined"); } } function processUser(user: User | null) { assertIsDefined(user, "User must exist"); // TypeScript знает, что user не null console.log(user.name); } Generic Constraints Extends keyword function getProperty (obj: T, key: K): T[K] { return obj[key]; } const user = { name: "John", age: 30 }; const name = getProperty(user, "name"); // OK const invalid = getProperty(user, "invalid"); // Error! Multiple Constraints interface Identifiable { id: string; } interface Timestamped { createdAt: Date; } function save (entity: T) { console.log(`Saving ${entity.id} created at ${entity.createdAt}`); } Infer keyword type UnwrapPromise = T extends Promise ? U : T; type A = UnwrapPromise >; // string type B = UnwrapPromise ; // number // Практический пример: Extract функции из объекта type FunctionPropertyNames = { [K in keyof T]: T[K] extends Function ? K : never; }[keyof T]; type Functions = Pick >; Recursive Types type Json = | string | number | boolean | null | Json[] | { [key: string]: Json }; const data: Json = { name: "John", age: 30, hobbies: ["reading", "gaming"], address: { city: "NYC", nested: { value: 123 } } }; Brand Types (Nominal Typing) type UserId = string & { readonly brand: unique symbol }; type PostId = string & { readonly brand: unique symbol }; function createUserId(id: string): UserId { return id as UserId; } function getUser(id: UserId) { // ... } const userId = createUserId("user-123"); const postId = "post-456" as PostId; getUser(userId); // OK getUser(postId); // Error: Type 'PostId' is not assignable to type 'UserId' Type-safe Event Emitter type EventMap = { click: { x: number; y: number }; change: { value: string }; submit: { data: FormData }; }; class TypedEventEmitter > { on (event: K, handler: (data: T[K]) => void) { // ... } emit (event: K, data: T[K]) { // ... } } const emitter = new TypedEventEmitter (); emitter.on("click", (data) => { console.log(data.x, data.y); // Autocomplete работает! }); emitter.emit("click", { x: 10, y: 20 }); // OK emitter.emit("click", { invalid: true }); // Error! Builder Pattern с Type Safety class QueryBuilder { private query: any = {}; select ( ...fields: K[] ): QueryBuilder { this.query.select = fields; return this as any; } where(condition: Partial ): this { this.query.where = condition; return this; } execute(): Promise []> { // Execute query return Promise.resolve([]); } } interface User { id: string; name: string; email: string; } const users = await new QueryBuilder () .select("name", "email") .where({ id: "123" }) .execute(); // users имеет тип Pick [] Best Practices Предпочитайте типы интерфейсам для объектов Используйте const assertions для литералов Включите strict mode в tsconfig.json Избегайте any — используйте unknown Используйте satisfies operator (TS 4.9+) const config = { url: "https://api.example.com", timeout: 5000, } satisfies Config; // Сохраняет literal types config.url; // "https://api.example.com", не просто string Performance Tips Используйте type вместо interface для unions Избегайте глубоких рекурсивных типов (>50 уровней) Используйте project references для больших монорепо Включите incremental compilation Debugging Types // Смотрите результирующий тип type Debug = { [K in keyof T]: T[K] }; type Result = Debug >; // Hover покажет: { name: string; email: string } Заключение: Продвинутый TypeScript — это не просто добавление типов, а проектирование type-safe API, которое делает невозможным написать неправильный код. Инвестиции в изучение advanced patterns окупаются через снижение количества багов, улучшение DX и ускорение рефакторинга. Практикуйтесь, изучайте type challenges, и ваш код станет не только безопаснее, но и элегантнее.