Types and inference
Type arrays
Describe a collection whose elements share a type and understand the equivalent bracket and generic syntax.
An array type answers one question: what can each element be? TypeScript gives you two ways to write the answer, and they mean exactly the same thing:
const names: string[] = ['Ada', 'Lin']
const moreNames: Array<string> = ['Grace']
string[] is the bracket form. Array<string> is the generic form. I use the bracket form almost everywhere, because it is shorter. The generic form reads better when the element type gets complex, like Array<string | number>, where the brackets would be easy to misplace.
You rarely need to write either one. Initialize an array with values and TypeScript infers the element type. const names = ['Ada', 'Lin'] is already a string[].
The element type guards every operation
Once TypeScript knows the element type, it checks everything you add later:
names.push('Margaret')
names.push(42)
The first call works. The second fails:
error TS2345: Argument of type 'number' is not assignable to parameter of type 'string'.
Reads are protected too. Every element you pull out of names is a string, so calling names[0].toUpperCase() is always safe as far as the type is concerned.
Inference flows through array methods
This is where arrays get pleasant. Array methods receive the element type through inference:
const uppercaseNames = names.map(name => name.toUpperCase())
You did not annotate name, but TypeScript knows it is a string, because names is a string[]. It also infers the result: toUpperCase() returns a string, so uppercaseNames is a string[]. Chain a .filter() or another .map() after it and the types keep flowing through.
Mixed elements need a union
Sometimes an array really does hold two kinds of values. Say so with a union:
const ids: Array<string | number> = ['a1', 42]
Now each element is string | number. When you read one, you have to check which it is before calling a string method or doing arithmetic. That extra step is the honest price of a mixed collection. Only pay it when the mix is intentional.
Do not widen to any[]
Here is the mistake to avoid. When a push fails, do not “fix” it by changing the type to any[]. That removes checking from every read afterwards. A typo like names[0].toUppercase() would compile fine and crash at runtime.
If TypeScript rejects a push, the element type is telling you something about your data. Either the value is wrong, or the array really needs a wider union. Decide which, and write it down in the type.
Try this on your own: create a number[], map it to formatted strings with toFixed(2), and hover over the result to confirm TypeScript inferred string[].
Lesson completed