Indexed Access Types

In the lesson on generics, you wrote Hero[K]: the type of the property K of Hero. The key in the square brackets can also be written straight away, as a literal type in quotes. Hero["heroClass"] is the type of the property heroClass of Hero. This is called an indexed access type:

interface Hero {
    name: string;
    heroClass: "warrior" | "mage" | "ranger";
    level: number;
}

function trainer(heroClass: Hero["heroClass"]): string {
    if (heroClass === "warrior") {
        return "Bruno, the master of arms";
    } else if (heroClass === "mage") {
        return "Ema, the archmage";
    }
    return "Tom, the old hunter";
}

console.log(trainer("mage"));
console.log(trainer("healer"));
main.ts(17,21): error TS2345: Argument of type '"healer"' is not assignable to parameter of type '"mage" | "ranger" | "warrior"'.

The parameter accepts exactly the classes from Hero, and TypeScript writes them out in the message. Why not write the union again? Because then the classes would be in two places. With Hero["heroClass"], they're written only once, in Hero. When a new class is added there, trainer accepts it too, and nobody has to remember to change it.

On a union of object types, as in the lesson on modeling data, you get the union of that property's types in all its members. Each quest has a status, so Quest["status"] is the union of all the statuses:

type Quest =
    | { status: "open"; title: string }
    | { status: "done"; title: string; reward: number }
    | { status: "failed"; title: string; reason: string };

type QuestStatus = Quest["status"]; // "open" | "done" | "failed"

Only the properties that every member has work this way: Quest["reward"] gives Property 'reward' does not exist on type 'Quest'.

Together with Record from the lesson on modeling data, you get an object with one value for every status: Record<Quest["status"], number> has the keys open, done and failed. You'll use it in the final project.

The key can also be the type number. On an array type, [number] means “the element at any index”: with type Names = string[];, Names[number] is string. That's the second half of the recipe (typeof classes)[number] from the lesson on modeling data: typeof classes is the type of the list, and [number] is the type of its elements. The first half, typeof, comes in the next exercise.

Typical mistakes:

  • A dot instead of the brackets. In types, Hero.heroClass doesn't work, but the message tells you what to write:
    main.ts(7,29): error TS2713: Cannot access 'Hero.heroClass' because 'Hero' is a type, but not a namespace. Did you mean to retrieve the type of the property 'heroClass' in 'Hero' with 'Hero["heroClass"]'?
  • The key without quotes, Hero[heroClass]. The key is a type, so it's a literal in quotes. Without them, TypeScript reads heroClass as a name. In trainer, that's the name of the parameter, a value, so you get:
    main.ts(7,34): error TS2538: Type 'heroClass' cannot be used as an index type.
    main.ts(7,34): error TS2749: 'heroClass' refers to a value, but is being used as a type here. Did you mean 'typeof heroClass'?
    Where nothing is called heroClass, you get Cannot find name 'heroClass'. instead of the TS2749 message.
  • A key that the type doesn't have, Hero["class"]: Property 'class' does not exist on type 'Hero'.

Instructions

Change the type of the parameter of printRarity from string to Item["rarity"] and run the program. TypeScript finds a typo in one of the calls. Fix it, and the program prints the legendary items too.

Under the calls, count the items of each rarity. Create a constant counts of type Record<Item["rarity"], number> with a 0 for each rarity. In a for...of loop over shop, add one to counts[item.rarity] for each item, and after the loop, print counts:

{ common: 2, rare: 1, legendary: 1 }

The shop has a new rarity, "epic". Add it to the union in Item, add the item { name: "Dragon Scale", rarity: "epic", price: 150 } at the end of shop, and call printRarity("epic") under the other two calls. TypeScript reports the place that needs to know about the new rarity; fix it. printRarity didn't need any change. The program ends like this:

legendary:
- Phoenix Feather (300 gold)
epic:
- Dragon Scale (150 gold)
{ common: 2, rare: 1, legendary: 1, epic: 1 }

Start programming for free

By signing up, you agree to the Terms of Service and Privacy Policy.

Or sign up with:

4/7