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:
readonlygoes only in front of[]types.readonly Array<string>gives'readonly' type modifier is only permitted on array and tuple literal types.Writereadonly string[]orReadonlyArray<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/50Start programming for free
3/7