🏠 Home / Hub

🔷 TypeScript Lesson 08 — Advanced Types

← Back to TS Menu  |  🏠 Hub

1. Conditional Types

// T extends U ? X : Y
// If T is assignable to U, result is X, else Y

type IsString<T> = T extends string ? true : false;
type IsArray<T>  = T extends any[]  ? true : false;

type A = IsString<string>;  // true
type B = IsString<number>;  // false
type C = IsArray<string[]>; // true
type D = IsArray<string>;   // false

// NonNullable — remove null/undefined
type NonNullable<T> = T extends null | undefined ? never : T;
type E = NonNullable<string | null | undefined>;  // string

// Unwrap array elements
type ElementOf<T> = T extends (infer U)[] ? U : T;
type F = ElementOf<string[]>;  // string
type G = ElementOf<number[]>;  // number
type H = ElementOf<string>;    // string (not array)

// ReturnType — extract function return type
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never;
function greet(): string { return "Hello"; }
type GreetReturn = ReturnType<typeof greet>;  // string

// Parameters — extract function params
type Parameters<T> = T extends (...args: infer P) => any ? P : never;
type GreetParams = Parameters<(name: string, age: number) => void>;  // [string, number]

2. Mapped Types

// Transform every key in an object type
type Readonly<T> = {
  readonly [K in keyof T]: T[K];
};

type Nullable<T> = {
  [K in keyof T]: T[K] | null;
};

type Optional<T> = {
  [K in keyof T]?: T[K];
};

// Remove readonly
type Mutable<T> = {
  -readonly [K in keyof T]: T[K];
};

// Remove optional
type Required<T> = {
  [K in keyof T]-?: T[K];
};

interface User {
  id:    number;
  name:  string;
  email: string;
}

type ReadonlyUser  = Readonly<User>;   // all readonly
type NullableUser  = Nullable<User>;   // all can be null
type PartialUser   = Optional<User>;   // all optional

// Filter keys by value type
type FilterByType<T, U> = {
  [K in keyof T as T[K] extends U ? K : never]: T[K];
};

interface Mixed {
  id:       number;
  name:     string;
  active:   boolean;
  count:    number;
}

type OnlyStrings  = FilterByType<Mixed, string>;  // { name: string }
type OnlyNumbers  = FilterByType<Mixed, number>;  // { id: number; count: number }

3. Template Literal Types

// String literal manipulation at type level

type EventName = 'click' | 'focus' | 'blur';

// Prefix with "on"
type EventHandler = `on${Capitalize<EventName>}`;
// "onClick" | "onFocus" | "onBlur"

// CSS property builder
type Side    = 'top' | 'right' | 'bottom' | 'left';
type Spacing = `margin-${Side}` | `padding-${Side}`;
// "margin-top" | "margin-right" | ... | "padding-top" | ...

// API endpoint builder
type Method   = 'get' | 'post' | 'put' | 'delete';
type Endpoint = '/users' | '/posts' | '/comments';
type Route    = `${Uppercase<Method>} ${Endpoint}`;
// "GET /users" | "POST /users" | "GET /posts" | ...

// Getter/Setter type generator
type Getter<T, K extends keyof T & string> = {
  [P in K as `get${Capitalize<P>}`]: () => T[P];
};
type Setter<T, K extends keyof T & string> = {
  [P in K as `set${Capitalize<P>}`]: (value: T[P]) => void;
};

interface UserProps { name: string; age: number; }

type UserGetters = Getter<UserProps, keyof UserProps & string>;
// { getName: () => string; getAge: () => number; }

type UserSetters = Setter<UserProps, keyof UserProps & string>;
// { setName: (v: string) => void; setAge: (v: number) => void; }

4. Discriminated Unions

// Discriminated Union = union with common literal type field
// TypeScript can narrow type based on that field

type LoadingState = {
  status: 'loading';
};

type SuccessState<T> = {
  status: 'success';
  data:   T;
};

type ErrorState = {
  status: 'error';
  error:  string;
  code:   number;
};

type AsyncState<T> = LoadingState | SuccessState<T> | ErrorState;

// Type narrowing via switch
function renderUser(state: AsyncState<User>): string {
  switch (state.status) {
    case 'loading':
      return '⏳ Loading...';

    case 'success':
      return `✅ ${state.data.name}`;  // TS knows: state.data exists

    case 'error':
      return `❌ Error ${state.code}: ${state.error}`;  // state.error exists

    default:
      const _exhaustive: never = state;  // exhaustiveness check!
      return _exhaustive;
  }
}

// Shape union example
type Shape =
  | { kind: 'circle';    radius: number }
  | { kind: 'rectangle'; width: number; height: number }
  | { kind: 'triangle';  base: number;  height: number };

function area(shape: Shape): number {
  switch (shape.kind) {
    case 'circle':    return Math.PI * shape.radius ** 2;
    case 'rectangle': return shape.width * shape.height;
    case 'triangle':  return 0.5 * shape.base * shape.height;
  }
}

5. infer — Type Inference

// infer = extract type from within a conditional type

// Unwrap Promise
type Awaited<T> = T extends Promise<infer R> ? R : T;
type A = Awaited<Promise<string>>;   // string
type B = Awaited<Promise<number>>;   // number
type C = Awaited<string>;             // string (not promise)

// Unwrap array
type UnwrapArray<T> = T extends Array<infer U> ? U : T;
type D = UnwrapArray<string[]>;  // string
type E = UnwrapArray<User[]>;    // User

// Extract function params and return
type FnParams<T>  = T extends (...args: infer P) => any ? P : never;
type FnReturn<T>  = T extends (...args: any[]) => infer R ? R : never;
type FnAsync<T>   = T extends (...args: any[]) => Promise<infer R> ? R : never;

async function fetchUser(id: number): Promise<User> { /* ... */ return {} as User; }

type FetchParams = FnParams<typeof fetchUser>;   // [id: number]
type FetchReturn = FnReturn<typeof fetchUser>;   // Promise<User>
type FetchData   = FnAsync<typeof fetchUser>;    // User (unwrapped!)
💡 infer is what powers built-in utility types: ReturnType, Parameters, Awaited, InstanceType

🎉 TypeScript Complete!

Types → Interfaces → Functions → Classes → Generics → Vue+TS → Decorators → Advanced Types

🏠 Hub 🔷 TS Menu

← TS 07  |  🏠 Back to Hub

📌 Study Checklist