TypeScript Advanced Patterns: Conditional Types and Template Literals

TypeScript’s type system is Turing-complete — which means you can encode complex logic entirely at the type level. Conditional types and template literal types are two of the most powerful features for writing types that are precise rather than merely descriptive. Here is when and how to use them.

Conditional Types

The syntax: `T extends U ? X : Y`. If `T` is assignable to `U`, the type resolves to `X`; otherwise `Y`. The simplest use: `type IsString = T extends string ? true : false;`. Useful when you need different types based on the input type. The `infer` keyword: used inside conditional types to capture a type from a pattern. Example: `type ReturnType = T extends (…args: any[]) => infer R ? R : never;` — this extracts the return type of any function. `infer` can appear on the right side of `extends` and creates a type variable that TypeScript fills in based on what was matched. Distributive conditional types: when a conditional type is applied to a union type, it distributes over each member. `type ToArray = T extends any ? T[] : never;` — `ToArray` evaluates as `string[] | number[]`, not `(string | number)[]`. To prevent distribution, wrap in a tuple: `type ToArray = [T] extends [any] ? T[] : never;`. Practical uses of conditional types: `NonNullable` (built-in — removes null and undefined from T); `Parameters` (extracts parameter types of a function); `ConstructorParameters` (parameter types of a constructor); `InstanceType` (return type of a constructor — the instance type). Building your own: `type Awaited = T extends Promise ? Awaited : T;` — recursively unwraps nested Promises. `type DeepPartial = T extends object ? { [P in keyof T]?: DeepPartial } : T;` — makes all nested properties optional. The constraint: conditional types that depend on `infer` or complex type manipulation can cause TypeScript to take significant time to check large codebases — be aware that excessive use of deep recursive conditional types slows down the compiler.

Template Literal Types

Template literal types (introduced in TypeScript 4.1) create string types from combinations of other string types: `type Greeting = \`Hello, \${string}\`;` — matches any string starting with “Hello, “. Combined with unions: `type EventName = \`on\${Capitalize}\`;`. More concretely: `type CSSProperty = ‘margin’ | ‘padding’; type CSSValue = number | string; type CSSDeclaration = \`\${CSSProperty}: \${CSSValue}\`;` — a type that matches “margin: 10px” but not “font-size: 12px”. Practical application — typed event names: `type Events = { click: MouseEvent; focus: FocusEvent; blur: FocusEvent; }; type EventListenerName = \`on\${Capitalize}\`;` — produces `”onClick” | “onFocus” | “onBlur”`. The utility types that use template literals: `Uppercase`, `Lowercase`, `Capitalize`, `Uncapitalize` — all operate on string types. Extracting parts of strings with template literals + infer: `type ExtractRoute = S extends \`/\${infer Rest}\` ? Rest : never;` — `ExtractRoute<'/users/profile'>` gives `’users/profile’`. Route parameter extraction: `type ExtractParams = S extends \`\${string}:\${infer Param}/\${infer Rest}\` ? Param | ExtractParams<\`/\${Rest}\`> : S extends \`\${string}:\${infer Param}\` ? Param : never;` — recursively extracts `:id` and `:userId` from route patterns.

When These Features Are Worth Using

Conditional types are worth the complexity when: you are writing library code where strong typing prevents a whole class of user errors; the alternative is a much larger set of overloads; you are encoding invariants that cannot be expressed as simple union or intersection types. Template literal types are worth using when: you have string enum values that follow predictable patterns (event names, CSS properties, route patterns, method names); you want to derive a set of valid strings from another set rather than maintain them in sync manually. When NOT to use them: for application code that changes frequently — complex type machinery is expensive to maintain and understand; when a simpler type (a union or a type alias) serves the same purpose; when the type error messages become incomprehensible to the people reading them. The readability principle: if your conditional type requires a comment to explain what it does, consider whether a simpler approximation (even if slightly less precise) would better serve the codebase.

上一篇 德国公共医疗体系:外籍人士需要了解的内容
下一篇 TypeScript高级模式:条件类型和模板字面量