This is a study note of TypeScript Handbook. The goal of TypeScript is to add structural types to JavaScript and statically check types. This handbook describes commonly-used TS syntax and patterns, compiler options and type system behaviors.

0 Essential TypeScript

  • A TS type is a set of values that share something in common.
  • TS is a structurally typed type system. Types are not reified – they are erased at runtime.
  • TS’s type system is very powerful because it allows expressing types in terms of other types.

1 The Basics

Given the code message.toLowerCase() or message(), the behavior of each operatoin depends on the value types:

  • is message callable?
  • does it have a property called toLowerCase?
  • if it does, is toLowerCase callable?
  • what does a callable return?

The type of a value determines its behaviors and capabilities. JS doesn’t give information like the types of a funciton’s inputs and output. JS provides dynamic typing: running the code to see what happens.

TS is a static types system describing the shapes and behaviors of values. TS catches a lot of legitimate type and logic bugs. It also brings code completions, auto fixes, refactorings, navigations and references. Types data are erased away in compliation. TS can also generate code for different targets such as ES3, ES5 etc.

2 Everyday Types

  • Use string, number and boolean for the three JS primitive types.
  • T[] for an array of type T.
  • any no type checking - use noImplicitAnay to disable implict any that happens when TS can’t infer a type from its context.
  • Add types to function inputs and output in its definition.
  • An object type is defined by listing its properties and their types. Optional properties has a ? after the property name.
  • Use T1 | T2 to define a Union Type whose value can be either one of many types.
  • A Type Aliase gives a name to a type or an aliase of an existing type.
  • An Interface is another way to define an object type. Interface can be re-opened to add new properties while type aliases are fixed.
  • Type assertion: use angle-bracked syntax <string> fun() or as operator fun() as string. Only less specific or more specific types allowed in type assertions. You can use (expr as any) as T to bypass the checking.
  • Literal types: const values of number and string have a literal type whose value is fixed. Literal types can be combined into unions to represent a set of known values. The boolean type can be thought as an alias of true | false. Use as const to convert an entire object to type literals, instead of their general version of string, number or boolean.
  • null and undefined: With strictNullChecks on, when a value is null or undefined, you will need to test for those values before using methods or properties on that value. Use ! after an expression is a type assertion that the value isn’t null or undefined.
  • Enums is not a type-level addtion to JS. It is a runtime feature.
  • bigint: let x = BigInt(100); let y = 100n
  • symbol: JS creates a globally unique reference via the Symbol() function that returns a value of type symbol.

3 Narrowing

3.1 Type Gurads

TS type analysis perform checks code on if/else, conditional ternaries, loops, and truthiness that can affect types. The specical checks are called type guards. They are used to refine types to more specific types than declared is called narrowing.

You should narrow the union when the value is used. Operators such as typeof, Array.isArray, instanceof can be used. If every member in a union has a property in common, you can use that property without narrowing.

The typeof operator guards a limite value types: undefined, boolean, number, bigint, string, symbol, function, and for all others object. A tricky thing is typeof null === 'object'.

TS use switch and eqaulity check === or !== to narrow types. Only common types are narrowing results.

The instanceof checks the prototype chain and narrows the resulting types.

An assignemnt is always checked against the declarred type but narrows the left side appropriately in each following assignemnts.

3.2 Truthiness Narrowing and Control Flow Analysis

In JS, constructs like if, ! or && first coerce their conditions to booleans to make sense of them. 0, NaN, ''(empty string), 0n (the bigint zero), null, and undefined are coerced to false, and other valules get coerced to true.

To explicitly coerce a value, use Boolean() function or !!. For the !!, TS infers a narrow literal boolean type of true or false.

TS conducts control flow analysis based on reachability where there are multiple branches.

3.3 The in Operator Narrowing

The in operator checks if an object has a property with a name. The true branch narrows to a type that has either an optional or required property of the specified name. The false branch narraows to a type that has an optional or missing property of the specified name. The optional property is in both branches.

3.4 Type Predicates

So far, the narrowings are based on JS constructs. You can control over how types change by defined a Type Predicate function – it is a user-defined type guard.

A predicate takes the form parameterName is Type, where parameterName must be the name of a parameter from the current function signature. Anytime the function is called, TypeScript will narrow that variable to that specific type if the original type is compatible. It is often used with as

3.5 Discriminated Unions

Use a common property kind wiht a literal type to define an interface/type, then define a union type that unions multiple interfaces/types is a common pattern called discriminated unions.

3.6 The never Type

When narrowing, you can reduce the options of a union to a point where you have removed all possibilities and have nothing left. In those cases, TypeScript will use a never type to represent a state which shouldn’t exist.

The never type is assignable to every type; however, no type is assignable to never (except never itself). In switch statement, add a default branch with assignment to a never variable helps to do an exhaustive checking.

4 Function Types

The simplest way to describe a function is with a funciton type expression that is similar to arrow functions: a list of function parameters annoted with their types, a fat arrow => followed by its return type.

If we want to describe something callable with properties, we can write a call signature in an object type that uses : between the parameter list and the return type rather than =>. Add a new operator when you define a constructor signature.

TS uses generics when we want to describe type relationships among multiple values.

We constrain a type parameter to another type by writing an extends clause such as <T extends {length: number}>.

When TS cannot infer the intended type arguments, you can specify the type explicitly.

You can mark a parameter optional with ?. You can also provide a parameter default.

To write overload signatures, write some number of function signatures, followed by a function implementation with a compatible signature called implementation signagure and a function body. The implementation signature can’t be called driectly. When writing an overloaded function, you should always have two or more signatures above the implementation of the function.

The JavaScript specification states that you cannot have a parameter called this, and so TypeScript uses that syntax space to let you declare the type for this in the function body. Note that you need to use function and not arrow functions to get this behavior because fat arrow function captures the contextual value of this.

void represents the return value of functions which don’t return a value. It’s the inferred type any time a function doesn’t have any return statements, or doesn’t return any explicit value from those return statements. void is not the same as undefined.

The special type object refers to any value that isn’t a primitive (string, number, bigint, boolean, symbol, null, or undefined). It is different from the global type Object. Function values are objects.

unknown type represent any value. This is similar to the any type, but is safer because it is not legal to do anything with an unknown value. This is useful when describing function types because you can describe functions that accept any value without having any values in your function body.

The never type represents values which are never observed: a function throws an exception or terminates execution of the program.

The global type Function describes properties like bind, call, apply, and others present on all function values in JavaScript. It also has the special property that values of type Function can always be called; these calls return any and should be avoided. The type () => void is generally safer.

A rest parameter appears after all other parameters, and uses the ... syntax. In TS, the type annotation on these parameters is implicitly any[] instead of any, and any type annotation given must be of the form Array<T>vor T[], or a tuple type .

In general, TS does not assume that arrays are immutable and this could lead to error “A spread argument must either have a tuple type or be passed to a rest parameter”. To fix it, use const after an array to make it a tuple.

The type annotation for the parameter destructuring object goes after the destructuring syntax. You can use a named type to avoid the verbose syntax.

5 Object Types

TS represent object data using anonymous object types that can be named by using either an interface or a type alias.

Each property in an object type can specify a couple of things: the type, whether the property is optional (?), and whether the property can be written to (readonly).

Index Signatures can be used to describe the types of possible index property values using [index: string]: T or [index: number]: T . An index signature property type must be string or/and number. While string index signatures are a powerful way to describe the “dictionary” pattern, they also enforce that all properties match their return type.

The extends keyword on an interface allows us to effectively copy members from other named types, and add whatever new members we want. Interfaces can also extend from multiple types.

TS provides another construct called intersection types that is mainly used to combine existing object types using & operator. The intersection type has all the members of the operands.

The principle difference between the extends and & in type combination is how conflicts are handled.

We can make a generic type using type parameter. Type aliases can also be generic. Since type aliases, unlike interfaces, can describe more than just object types, we can also use them to write other kinds of generic helper types.

The Array<Type> is a generic type. Similarly, there are Map<K, V>, Set<T>, and Promise<T>. You can assign a regular array to a ReadonlyArray variable to make it read only. Type[] is a shorthand for Array<Type>, and readonly Type[] is a shorthand for ReadonlyArray[Type].

A tuple type is another sort of Array type that knows exactly how many elements it contains, and exactly which types it contains at specific positions. Tuples can have optional properties by writing out a question mark (? after an element’s type). Optional tuple elements can only come at the end, and also affect the type of length. Tuples can also have rest elements, which have to be an array/tuple type. The optional and rest elements allow TS to correspond tuples with parameter lists. You can have readonly tuple types. Array literals with const assertions will be inferred with readonly tuple types.