How to think about TypeScript
TypeScript is a superset of JavaScript, if one avoids certain features. Under the hood, transpiling TypeScript to JavaScript is simply removing the type annotations. In other words, the actual running code isn’t anymore powerful than JavaScript itself.
Additionally, TypeScript doesn’t offer simpler syntax than JavaScript. TypeScript code strictly longer than JavaScript code. What is the point of TypeScript if it does not provide more capabilities at runtime and the code is longer?
The purpose of types
Types allow us to write more precise code to detect errors before running it. Ultimately we still need to unit test all our code as unit tests are exercising JavaScript, not TypeScript.
// Incorrect type is detected by TypeScript
nice('69') // fails with: Argument of type 'string' is not assignable to parameter of type 'number'.
// Implementation errors still need to be prevented with unit tests
expect(nice(69)).toBe(true)
expect(nice(42)).toBe(false)
Type are like unit tests as errors indicate something is wrong with the implementation. With a properly configured editor and sound types, one can detect bugs as the keys are pressed. This is why developers are so excited to use typed languages.
Where do types come from?
Types are mainly inferred by TypeScript but they can also be declared. The inferred types are usually good enough but declaring types gives us an opportunity to name things.
const inferredThing = {
name: 'Bob'
}inferredThing.age = 42 // fails with: Property 'age' does not exist on type '{ name: string; }'.
type Person = {
name: string
}
const explicitThing: Person = {
name: 'Bob'
}explicitThing.age = 42 // fails with: Property 'age' does not exist on type 'Person'.
inferredThing
doesn’t have a declared type and TypeScript infers the type to be { name: string; }
. Note the inferred type is in the error message.
But by creating the type alias Person
, we get a more obvious error message.
Complexity tippling point
Relying on inferred types with judicious type aliases will get one really far with TypeScript. However, at some point one will run into a non-obvious type error. The first inclination might be to reach for type assertions to make the problem go away, but typically that would only make the problem worse. Type assertions are the last resort for those who truly know better than the TypeScript compiler. Remember types are not for runtime correctness, types are for us developers as we are writing the code. A bad type assertion will ruin the very protection we desire from types.
Instead of type assertions, one will likely need to use advanced features of TypeScript to retain type soundness. These features are dedicated towards towards maintaining the types we already have. A few quick examples on what TypeScript is capable of,
- Union types—Allows one to define a type to mean “this type or that type”. IE.
{name: string} | {age: number}
is the type of either an object of{name: string}
or an object of{age: number}
, but not an object with both fields nor an empty object. - Intersection types — Allows one to define a type mean “merge of this type and that type”. IE.
{name: string} & {age: number}
is the same as the type{name: string, age: number}
. - Generics—Allows one to refer to an unknown (hence generic) type and pass it along, thus retaining type information
- Conditional types—Allows one to “unwrap type”. For example, able to convert the type
Promise<T>
toT
. Also used to retain inferred array types - Mapped types — Allows create a typed object from another. For example, able to convert the type
{name: 'Alice' | 'Bob'}
to{Bob: boolean}
. Also used to retain inferred object types
Are we your type? You’re in luck, Battlefy is hiring.