I’m very familiar with TypeScript so let’s see if the book offers any new insights.
TypeScript is unique in that it doesn’t compile to Bytecode but rather it compiles to JavaScript, which you can then run on any browser. The main function of TypeScript is to typecheck your code, and what makes it magic is that you can type check your code live while you are typing it, rather than waiting for compile time. TypeScript does actually compile your code but with live reloading it can statically analyze your code for type errors and it seems instantaneous.
TypeScript is helpful because JavaScript itself is awful:
3 + [1]; // evaluates to "31"
0.1 + 0.2; // evaluates to 0.30000000000000004
let k = 1;
k += "1"; // evaluates to "11"
Regarding the types, you can specify the type with annotations:
let a: number = 1;
let b: any = 2;
let c: unknown = 3;
The type unknown is really interesting because you need to validate the type before you can use it:
let c: unknown = 3;
if (typeof c === 'number') {
let d = a + c;
}
There are also new types besides number available:
let e: bigint = 100n // bigint
let f: 100n = 100n // 100n
Symbols are also a feature that is a new addition or used infrequently, symbols are unique even if they have the same value:
let g: symbol = Symbol('x');
let h: symbol = Symbol('x');
g === h; // evaluates to 'false'
When it comes to functions, you can use a special arguments keyword to handle multiple function parameters… that’s a neat trick. It’s like an array but not really so you need to convert it to an array if you want to use functions like reduce on the arguments.
function sum() {
return Array.from(arguments).reduce((total, n) => total + n, 0)
}
sum(1, 2, 3); // evaluates to '6'
sum(1, 2, 3, 4, 5); // evaluates to '15'
But it’s not safe in TypeScript:
sum(1, "2", 3); // evaluates to "123"
We can use rest parameters to add type checking:
function sum(...numbers: number[]): number {
return numbers.reduce((total, n) => total + n, 0)
}
sum(1, 2, 3); // evaluates to '6'
sum(1, "2", 3); // Argument of type 'string' is not assignable to parameter of type 'number'
The author states that TypeScript classes borrow heavily from C# which makes sense as they’re both from the Microsoft ecosystem. Your classes can’t extend types but they can extend interfaces, which are similar:
interface Animal {
eat(food: string): void
sleep(hours: number): void
}
class Cat implements Animal {
eat(food: string) {
console.info('Ate some', food, '. Mmm!')
}
sleep(hours: number) {
console.info('Slept for', hours, 'hours')
}
}
You can also extend interfaces the same way you can extend types. You can’t use traits with JavaScript classes but you can use mixins which are just higher level wrappers, I won’t go into more details here. There is also experimental support at the moment for decorators to make this easier:
@serializable
class APIPayload {
getValue(): Payload {
// ...
}
}
This replaces the more verbose wrapper syntax:
let APIPayload = serializable(class APIPayload {
getValue(): Payload {
// ...
}
})
TypeScript also doesn’t support the final keyword which might be a good thing? I know there’s been lots of drama in the PHP world about whether libraries should use it or not to prevent overriding classes or functions.
Although JavaScript isn’t technically an object-oriented language you can implement some design patterns with it:
type Shoe = {
purpose: string
}
class BalletFlat implements Shoe {
purpose = 'dancing'
}
class Boot implements Shoe {
purpose = 'woodcutting'
}
class Sneaker implements Shoe {
purpose = 'walking'
}
// Factory Pattern
let Shoe = {
create(type: 'balletFlat' | 'boot' | 'sneaker'): Shoe {
switch (type) {
case 'balletFlat': return new BalletFlat
case 'boot': return new Boot
case 'sneaker': return new Sneaker
}
}
}
Shoe.create('boot') // Shoe
TypeScript has a world-class type system that supports powerful type-level programming features that might make even the crotchetiest Haskell programmer jealous.
Nice.
TypeScript supports subtypes and supertypes, you can also do unions (|) and intersections (&) on types which was covered earlier in the book.
TypeScript also has a built in type Record to describe an object as a map from something to something.
type Weekday = 'Mon' | 'Tue'| 'Wed' | 'Thu' | 'Fri'
type Day = Weekday | 'Sat' | 'Sun'
let nextDay: Record<Weekday, Day> = {
Mon: 'Tue',
Tue: 'Wed',
Wed: 'Thu',
// etc
}
nextDay.Wed
You can also use mapped types:
let nextDay: {[K in Weekday]: Day} = {
Mon: 'Tue',
Tue: 'Wed',
// etc
}
Along with Record there are additional built-in types for more mapped types:
Partial<Object>: marks every field inObjectas optionalRequired<Object>: marks every field inObjectas nonoptionalReadonly<Object>: marks every field inObjectas read-onlyPick<Object, Keys>: returns a subtype ofObjectwith just the givenKeys
This is great. The following is called the Companion Object Pattern, and comes from Scala:
type Currency = {
unit: 'EUR' | 'GBP' | 'JPY' | 'USD'
value: number
}
let Currency = {
DEFAULT: 'USD',
from(value: number, unit = Currency.DEFAULT): Currency {
return {unit, value}
}
}
Now the magic, is that because types live in their own namespace, you can have the name Currency bound to both the type and the value! Which means you can import and use both at the same time:
import { Currency } from './Currency'
let amountDue: Currency = {
unit: 'JPY',
value: 9999
}
let otherAmountDue = Currency.from(333, 'EUR')
That’s a very cool trick… it would probably be the same as doing the following:
import { Currency, type Currency } from './Currency'
The rest of the book is about different types of JavaScript web frameworks, and using async programming and workers/threads, how to import modules, etc.
The book was OK, I learned a few new things… but I wouldn’t recommend reading it from front-to-back as a way to learn TypeScript as I think it’s better to gradually adopt it, so if you’ve already started programming in TypeScript and are curious to learn more then the book is great.