TypeScript Advanced Types¶
TypeScript's type system is a programming language itself - types can be computed, transformed, and composed using generics, conditional types, mapped types, and recursion.
Built-in Utility Types¶
// Object
Partial<T> // All optional
Required<T> // All required
Readonly<T> // All readonly
Pick<T, K> // Subset of properties
Omit<T, K> // Exclude properties
Record<K, V> // Object with keys K, values V
// Union
Exclude<T, U> // Remove types from union
Extract<T, U> // Keep only assignable to U
NonNullable<T> // Remove null/undefined
// Function
ReturnType<T> // Return type
Parameters<T> // Param types as tuple
// String
Uppercase<T> Lowercase<T> Capitalize<T> Uncapitalize<T>
// Promise
Awaited<T> // Unwrap Promise recursively
Type Assertions¶
const el = document.getElementById("input") as HTMLInputElement;
const value = someValue as unknown as TargetType; // Double (rare)
Branded Types¶
Nominally distinct types with same underlying structure:
type USD = number & { __brand: "USD" };
type EUR = number & { __brand: "EUR" };
function usd(amount: number): USD { return amount as USD; }
const price: USD = usd(100);
// const wrong: EUR = price; // Error!
Prevents accidental mixing of semantically different values.
Custom Type Guards¶
function isString(v: unknown): v is string {
return typeof v === "string";
}
function isUser(v: unknown): v is User {
return typeof v === "object" && v !== null && "name" in v;
}
// Assertion function
function assertString(v: unknown): asserts v is string {
if (typeof v !== "string") throw new Error("Not string");
}
// After call, TS narrows the type
assertString(value);
value.toUpperCase(); // OK
Conditional Types¶
type IsString<T> = T extends string ? true : false;
// Distributive over unions
type ToArray<T> = T extends any ? T[] : never;
type R = ToArray<string | number>; // string[] | number[]
// Prevent distribution with tuple wrapper
type ToArray<T> = [T] extends [any] ? T[] : never;
// infer: extract types
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
type ElementType<T> = T extends (infer E)[] ? E : T;
type UnwrapPromise<T> = T extends Promise<infer V> ? V : T;
Advanced Mapped Types¶
// Remap keys
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};
// Filter by value type
type StringKeys<T> = {
[K in keyof T as T[K] extends string ? K : never]: T[K];
};
// Make specific properties optional
type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
Recursive Types¶
type DeepReadonly<T> = {
readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};
type DeepPartial<T> = {
[K in keyof T]?: T[K] extends object ? DeepPartial<T[K]> : T[K];
};
type JSONValue = string | number | boolean | null | JSONValue[] | { [k: string]: JSONValue };
Template Literal Types¶
type Color = "red" | "blue" | "green";
type DarkColor = `dark-${Color}`; // "dark-red" | "dark-blue" | "dark-green"
Zod (Runtime Validation)¶
import { z } from "zod";
const UserSchema = z.object({
name: z.string().min(1).max(100),
age: z.number().int().positive(),
email: z.string().email(),
role: z.enum(["admin", "user"])
});
type User = z.infer<typeof UserSchema>; // TS type from schema
const result = UserSchema.safeParse(unknownData);
if (result.success) result.data; // Fully typed
else result.error; // Validation errors
Bridges compile-time types and runtime validation - define once, get both.
TypeScript with React¶
interface ButtonProps {
label: string;
variant?: "primary" | "secondary";
onClick: () => void;
children?: React.ReactNode;
}
const Button: React.FC<ButtonProps> = ({ label, variant = "primary", onClick }) => (
<button className={`btn-${variant}`} onClick={onClick}>{label}</button>
);
// Event types
const handleClick = (e: React.MouseEvent<HTMLButtonElement>) => {};
const handleChange = (e: React.ChangeEvent<HTMLInputElement>) => {};
const handleSubmit = (e: React.FormEvent<HTMLFormElement>) => {};
// useState
const [user, setUser] = useState<User | null>(null);
// Generic component
interface ListProps<T> {
items: T[];
renderItem: (item: T) => React.ReactNode;
}
function List<T>({ items, renderItem }: ListProps<T>) {
return <ul>{items.map(renderItem)}</ul>;
}
Gotchas¶
- Distributive conditionals:
T extends U ? X : Ydistributes over union T; wrap in tuple[T]to prevent inferonly in conditional: can only useinferin the extends clause of conditional types- Recursive depth limit: TS has a recursion depth limit (~50); deep types may fail
asis unsafe: type assertions bypass checking; prefer type guards- Mapped type key remapping:
asin mapped types can filter keys by mapping tonever
See Also¶
- typescript fundamentals - Basic types, generics, narrowing
- react components and jsx - Component typing patterns
- react state and hooks - Hook typing