Read-Only Arrays

A function doesn't get its own copy of an array: it gets the same array that the caller has. So it can change an array that isn't its own. This function should count the potions in a bag, and it does, but look at the bag afterwards:

const bag = ["Potion", "Rope", "Potion"];

function countPotions(items: string[]): number {
    let count = 0;
    while (items.length > 0) {
        if (items.pop() === "Potion") {
            count++;
        }
    }
    return count;
}

console.log(countPotions(bag));
console.log(bag);

This prints:

2
[]

pop() takes the last element out of the array, so after counting, the bag is empty. In a big program, a bug like this is hard to find: the bag empties in a function that only seemed to count. TypeScript can prevent it. Write readonly in front of the array type:

function countPotions(items: readonly string[]): number {
main.ts(6,19): error TS2339: Property 'pop' does not exist on type 'readonly string[]'.

A readonly string[] is an array that you can read but not change. Reading an element, length, for...of, includes, find, map and filter all work (map and filter make a new array). But there's no push, no pop, and no assigning to an element: items[0] = "Sword" gives Index signature in type 'readonly string[]' only permits reading. In a parameter, readonly is a promise that the caller can see in the function's first line: this function only reads the array.

You can also write the same type as ReadonlyArray<string>, just as string[] can be written as Array<string>. In error messages, TypeScript writes readonly string[].

A normal array can go into a readonly parameter: the function only reads it. The other way round doesn't work. A read-only array, such as a list made with as const in the lesson on modeling data, can't go into a string[] parameter, because that function could change it:

const classes = ["warrior", "mage", "ranger"] as const;

function printAll(names: string[]): void {
    for (const name of names) {
        console.log(name);
    }
}

printAll(classes);
main.ts(9,10): error TS4104: The type 'readonly ["warrior", "mage", "ranger"]' is 'readonly' and cannot be assigned to the mutable type 'string[]'.

So a function that only reads an array should take a readonly one: then it accepts both. And when it needs to return an array with something added, it makes a new one with spread: [...items, "Old Map"].

For objects, there's a utility type: Readonly<Hero> is Hero with every property readonly. It's like writing readonly in front of each property, as in the lesson on object types, but only where you use it: hero: Readonly<Hero> in a parameter means that the function doesn't change the hero, and hero.health = 0 inside it gives Cannot assign to 'health' because it is a read-only property. Like every type, readonly is checked only by TypeScript. The JavaScript that runs doesn't know about it.

Typical mistakes:

  • readonly goes only in front of [] types. readonly Array<string> gives 'readonly' type modifier is only permitted on array and tuple literal types. Write readonly string[] or ReadonlyArray<string>.
  • Returning the parameter itself from a function that returns string[]: The type 'readonly string[]' is 'readonly' and cannot be assigned to the mutable type 'string[]'. Return a new array.

Instructions

The function withItem should return a new list, but it changes the one it gets. Change the type of its parameter items to readonly string[] and run the program: TypeScript reports the line with push. Replace the body of the function with return [...items, item];.

Under the functions, print withItem(inventory, "Old Map"), and then inventory. The inventory hasn't changed:

["Healing Potion", "Rope", "Torch", "Old Map"]
["Healing Potion", "Rope", "Torch"]

The function healthText mustn't change the hero either. Change the type of its parameter to Readonly<Hero> and run the program. TypeScript finds a line in the function that does change the hero. Fix the mistake: a single = assigns a value, and you want to compare.

Under the other lines, print healthText(kira):

Kira: 34/50

Start programming for free

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

Or sign up with:

3/7