liveBook · Manning

Conditional type

As explained in Essential TypeScript 5, Third Edition

A conditional type is a TypeScript type expression that selects one of two result types based on a relationship involving a type parameter.

A conditional type chooses a result type from a relationship involving a generic type parameter. Its `extends` test resembles a ternary expression, but the choice is made by the TypeScript compiler, not by JavaScript at runtime. section 13.6

Choose a type from a boolean type argument

In `src/index.ts`: ```ts type resultType<T extends boolean> = T extends true ? string : number; let firstVal: resultType<true> = "String Value"; let secondVal: resultType<false> = 100; let mismatchCheck: resultType<false> = "String Value"; // error ``` The last declaration fails because `resultType<false>` resolves to `number`. The `boolean` constraint limits which type arguments the alias accepts. section 13.6

A conditional type can also connect an input choice to a method’s result. For a `Collection<T>.total` method, a `true` formatting argument selects a `string` result; `false` selects `number`. The compiler can infer the relevant type argument from that value. This is more concise than function overloads, though it can be harder to read. section 13.6.2

Listing 13.25 Defining a generic type in the index.ts file in the src folder
import { City, Person, Product, Employee } from "./dataTypes.js";

type resultType<T extends boolean> = T extends true ? string : number;

class Collection<T> {
    private items: T[];
  
    constructor(...initialItems: T[]) {
        this.items = initialItems || [];
    }
 
    total<P extends keyof T, U extends boolean>(propName: P, format: U)
            : resultType<U> {
        let totalValue = this.items.reduce((t, item) => 
            t += Number(item[propName]), 0);
        return format ? `$${totalValue.toFixed()}` : totalValue as any;
    }
}

let data = new Collection<Product>(new Product("Kayak", 275), 
    new Product("Lifejacket", 48.95));
 
let firstVal: string = data.total("price", true);
console.log(`Formatted value: ${firstVal}`);
let secondVal: number = data.total("price", false);
console.log(`Unformatted value: ${secondVal}`);

The complete `src/index.ts` listing shows the conditional return type beside the method implementation and calls that assign its results to `string` and `number` variables. section 13.6.2

There is a practical limit: the compiler may not correlate return values inside a method with its conditional return type. The `total` implementation uses an `any` assertion to get past that difficulty. Check both branches yourself; the assertion does not make an incorrect return value safe. section 13.6.2 section 13.6

Transform selected properties

A conditional type inside a mapped type can change only properties whose types match a target: ```ts type changeProps<T, U, V> = { [P in keyof T]: T[P] extends U ? V : T[P] }; type modifiedProduct = changeProps<Product, number, string>; ``` For a `Product` with `name` and numeric `price`, `modifiedProduct` has string types for both properties. A conversion function supplies the formatted price value; the type transformation does not perform that conversion. section 13.6.4

Accept only property names of a given type

To constrain a numeric-total function’s property name, map each matching property to its own name and each other property to `never`: ```ts type unionOfTypeNames<T, U> = { [P in keyof T]: T[P] extends U ? P : never; }; type propertiesOfType<T, U> = unionOfTypeNames<T, U>[keyof T]; ``` For `Product` and `number`, indexed access yields `"price"` after `never` is removed from the union. That union can constrain the function’s property-name parameter. section 13.6.5

The `infer` keyword handles types that must be discovered within a condition. For example, `T extends (infer U)[] ? keyof U : keyof T` selects an array element’s keys when `T` is an array, rather than the array’s own keys. It can also extract a function’s result type; built-in conditional types include `ReturnType<T>` and `InstanceType<T>`. section 13.6.6

Use conditional types when simpler generic types cannot express the required relationship. As conditions grow, review their possible type combinations: an overly broad result can leave a type-checking hole, while an overly narrow one rejects valid uses. The types themselves are removed during compilation. section 13.6 section 13.7

How Essential TypeScript 5, Third Edition covers it

How deep it goes
Works through definitions, compiler errors, mapped types, and inference while warning about assertions and complex conditions.
Examples
Shows boolean-selected variables, a formatted collection total, product-property conversion, numeric-property filtering, and inferred array keys.
What it assumes
Assumes familiarity with TypeScript generics and JavaScript arrays; develops conditional syntax before combining it with mappings and inference.
What you can do after
Build it from scratch to express input-dependent results and restrict property names, while checking branches that need assertions.
How it connects
Connects generic types with function overloads, mapped types, indexed access types, `keyof`, and type inference.

Related concepts

This explainer was generated by Manning's knowledge graph from the text of Essential TypeScript 5, Third Edition. The section links point to the parts of the book it draws on.