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
andboolean
for the three JS primitive types. T[]
for an array of typeT
.any
no type checking - usenoImplicitAnay
to disable implictany
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()
oras
operatorfun() 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 ofnumber
andstring
have a literal type whose value is fixed. Literal types can be combined into unions to represent a set of known values. Theboolean
type can be thought as an alias oftrue | false
. Useas const
to convert an entire object to type literals, instead of their general version ofstring
,number
orboolean
. null
andundefined
: WithstrictNullChecks
on, when a value isnull
orundefined
, 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’tnull
orundefined
.- 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 theSymbol()
function that returns a value of typesymbol
.
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.