Typescript Book
Typescript Book
Simone Poggiali
1
The Concise TypeScript Book
The Concise TypeScript Book provides a comprehensive and succinct
overview of TypeScript’s capabilities. It offers clear explanations
covering all aspects found in the latest version of the language, from
its powerful type system to advanced features. Whether you’re a
beginner or an experienced developer, this book is an invaluable
resource to enhance your understanding and proficiency in
TypeScript.
Translations
This book has been translated into several language versions,
including:
Chinese
2
https://github.com/gibbok/typescript-book/tree/main/downloads
https://gibbok.github.io/typescript-book
Table of Contents
The Concise TypeScript Book
Translations
Downloads and website
Table of Contents
Introduction
About the Author
TypeScript Introduction
What is TypeScript?
Why TypeScript?
TypeScript and JavaScript
TypeScript Code Generation
Modern JavaScript Now (Downleveling)
Getting Started With TypeScript
Installation
Configuration
TypeScript Configuration File
target
lib
strict
module
moduleResolution
esModuleInterop
jsx
skipLibCheck
files
include
exclude
3
importHelpers
Migration to TypeScript Advice
Exploring the Type System
The TypeScript Language Service
Structural Typing
TypeScript Fundamental Comparison Rules
Types as Sets
Assign a type: Type Declarations and Type Assertions
Type Declaration
Type Assertion
Ambient Declarations
Property Checking and Excess Property Checking
Weak Types
Strict Object Literal Checking (Freshness)
Type Inference
More Advanced Inferences
Type Widening
Const
Const Modifier on Type Parameters
Const assertion
Explicit Type Annotation
Type Narrowing
Conditions
Throwing or returning
Discriminated Union
User-Defined Type Guards
Primitive Types
string
boolean
number
bigInt
Symbol
null and undefined
Array
any
Type Annotations
4
Optional Properties
Readonly Properties
Index Signatures
Extending Types
Literal Types
Literal Inference
strictNullChecks
Enums
Numeric enums
String enums
Constant enums
Reverse mapping
Ambient enums
Computed and constant members
Narrowing
typeof type guards
Truthiness narrowing
Equality narrowing
In Operator narrowing
instanceof narrowing
Assignments
Control Flow Analysis
Type Predicates
Discriminated Unions
The never Type
Exhaustiveness checking
Object Types
Tuple Type (Anonymous)
Named Tuple Type (Labeled)
Fixed Length Tuple
Union Type
Intersection Types
Type Indexing
Type from Value
Type from Func Return
Type from Module
5
Mapped Types
Mapped Type Modifiers
Conditional Types
Distributive Conditional Types
infer Type Inference in Conditional Types
Predefined Conditional Types
Template Union Types
Any type
Unknown type
Void type
Never type
Interface and Type
Common Syntax
Basic Types
Objects and Interfaces
Union and Intersection Types
Built-in Type Primitives
Common Built-in JS Objects
Overloads
Merging and Extension
Differences between Type and Interface
Class
Class Common Syntax
Constructor
Private and Protected Constructors
Access Modifiers
Get and Set
Auto-Accessors in Classes
this
Parameter Properties
Abstract Classes
With Generics
Decorators
Class Decorators
Property Decorator
Method Decorator
6
Getter and Setter Decorators
Decorator Metadata
Inheritance
Statics
Property initialization
Method overloading
Generics
Generic Type
Generic Classes
Generic Constraints
Generic contextual narrowing
Erased Structural Types
Namespacing
Symbols
Triple-Slash Directives
Type Manipulation
Creating Types from Types
Indexed Access Types
Utility Types
Awaited<T>
Partial<T>
Required<T>
Readonly<T>
Record<K, T>
Pick<T, K>
Omit<T, K>
Exclude<T, U>
Extract<T, U>
NonNullable<T>
Parameters<T>
ConstructorParameters<T>
ReturnType<T>
InstanceType<T>
ThisParameterType<T>
OmitThisParameter<T>
ThisType<T>
7
Uppercase<T>
Lowercase<T>
Capitalize<T>
Uncapitalize<T>
NoInfer<T>
Others
Errors and Exception Handling
Mixin classes
Asynchronous Language Features
Iterators and Generators
TsDocs JSDoc Reference
@types
JSX
ES6 Modules
ES7 Exponentiation Operator
The for-await-of Statement
New target meta-property
Dynamic Import Expressions
“tsc –watch”
Non-null Assertion Operator
Defaulted declarations
Optional Chaining
Nullish coalescing operator
Template Literal Types
Function overloading
Recursive Types
Recursive Conditional Types
ECMAScript Module Support in Node
Assertion Functions
Variadic Tuple Types
Boxed types
Covariance and Contravariance in TypeScript
Optional Variance Annotations for Type Parameters
Template String Pattern Index Signatures
The satisfies Operator
Type-Only Imports and Export
8
using declaration and Explicit Resource Management
await using declaration
Import Attributes
Introduction
Welcome to The Concise TypeScript Book! This guide equips you
with essential knowledge and practical skills for effective TypeScript
development. Discover key concepts and techniques to write clean,
robust code. Whether you’re a beginner or an experienced developer,
this book serves as both a comprehensive guide and a handy
reference for leveraging TypeScript’s power in your projects.
LinkedIn: https://www.linkedin.com/in/simone-poggiali
GitHub: https://github.com/gibbok
Twitter: https://twitter.com/gibbok_coding
📧
Email: gibbok.coding gmail.com
9
TypeScript Introduction
What is TypeScript?
TypeScript is a strongly typed programming language that builds on
JavaScript. It was originally designed by Anders Hejlsberg in 2012
and is currently developed and maintained by Microsoft as an open
source project.
Why TypeScript?
TypeScript is a strongly typed language that helps prevent common
programming mistakes and avoid certain kinds of run-time errors
before the program is executed.
10
Tooling support with IntelliSense
Files with the extension .tsx or .jsx can contain JavaScript Syntax
Extension JSX, which is used in React for UI development.
11
Property 'y' does not exist on type '{ x: number; }'.
12
'use strict';
const add = (a, b) => a + b;
const result = add('x', 'y'); // xy
As the types are erased after compilation, there is no way to run this
code in JavaScript. To recognize types at runtime, we need to use
another mechanism. TypeScript provides several options, with a
common one being “tagged union”. For example:
interface Dog {
kind: 'dog'; // Tagged union
bark: () => void;
}
interface Cat {
kind: 'cat'; // Tagged union
meow: () => void;
}
type Animal = Dog | Cat;
13
}
};
14
if (mammal instanceof Dog) {
mammal.bark();
} else {
mammal.meow();
}
};
Here are some of the modern JavaScript features that can be used in
TypeScript:
15
Variables declaration using “let” or “const” instead of “var”.
“for-of” loop or “.forEach” instead of the traditional “for” loop.
Arrow functions instead of function expressions.
Destructuring assignment.
Shorthand property/method names and computed property
names.
Default function parameters.
Installation
Visual Studio Code provides excellent support for the TypeScript
language but does not include the TypeScript compiler. To install the
TypeScript compiler, you can use a package manager like npm or
yarn:
npm install typescript --save-dev
or
yarn add typescript --dev
Make sure to commit the generated lockfile to ensure that every team
member uses the same version of TypeScript.
or
16
yarn tsc
or installing it globally:
npm install -g typescript
If you are using Microsoft Visual Studio, you can obtain TypeScript
as a package in NuGet for your MSBuild projects. In the NuGet
Package Manager Console, run the following command:
Install-Package Microsoft.TypeScript.MSBuild
Configuration
TypeScript can be configured using the tsc CLI options or by utilizing
a dedicated configuration file called tsconfig.json placed in the root
of the project.
17
tsc --init
Here are some examples of CLI commands that run with the default
settings:
tsc main.ts // Compile a specific file (main.ts) to JavaScript
tsc src/*.ts // Compile any .ts files under the 'src' folder to
JavaScript
tsc app.ts util.ts --outfile index.js // Compile two TypeScript
files (app.ts and util.ts) into a single JavaScript file (index.js)
Notes:
At the following link you can find the complete documentation and
its schema:
https://www.typescriptlang.org/tsconfig
http://json.schemastore.org/tsconfig
18
target
lib
The “lib” property is used to specify which library files to include at
compilation time. TypeScript automatically includes APIs for
features specified in the “target” property, but it is possible to omit or
pick specific libraries for particular needs. For instance, if you are
working on a server project, you could exclude the “DOM” library,
which is useful only in a browser environment.
strict
The “strict” property enables stronger guarantees and enhances type
safety. It is advisable to always include this property in your project’s
tsconfig.json file. Enabling the “strict” property allows TypeScript to:
module
The “module” property sets the module system supported for the
compiled program. During runtime, a module loader is used to locate
and execute dependencies based on the specified module system.
19
The most common module loaders used in JavaScript are Node.js
CommonJS for server-side applications and RequireJS for AMD
modules in browser-based web applications. TypeScript can emit
code for various module systems, including UMD, System, ESNext,
ES2015/ES6, and ES2020.
moduleResolution
esModuleInterop
The “esModuleInterop” property allows import default from
CommonJS modules that did not export using the “default” property,
this property provides a shim to ensure compatibility in the emitted
JavaScript. After enabling this option we can use import MyLibrary
from "my-library" instead of import * as MyLibrary from "my-library".
jsx
The “jsx” property applies only to .tsx files used in ReactJS and
controls how JSX constructs are compiled into JavaScript. A
common option is “preserve” which will compile to a .jsx file keeping
unchanged the JSX so it can be passed to different tools like Babel
for further transformations.
20
skipLibCheck
The “skipLibCheck’’ property will prevent TypeScript from type-
checking the entire imported third-party packages. This property will
reduce the compile time of a project. TypeScript will still check your
code against the type definitions provided by these packages.
files
The “files” property indicates to the compiler a list of files that must
always be included in the program.
include
The “include” property indicates to the compiler a list of files that we
would like to include. This property allows glob-like patterns, such as
“*” for any subdirectory, ”” for any file name, and “?” for optional
characters.
exclude
The “exclude” property indicates to the compiler a list of files that
should not be included in the compilation. This can include files such
as “node_modules” or test files. Note: tsconfig.json allows
comments.
importHelpers
TypeScript uses helper code when generating code for certain
advanced or down-leveled JavaScript features. By default, these
helpers are duplicated in files using them. The importHelpers option
imports these helpers from the tslib module instead, making the
JavaScript output more efficient.
21
Migration to TypeScript Advice
For large projects, it is recommended to adopt a gradual transition
where TypeScript and JavaScript code will initially coexist. Only
small projects can be migrated to TypeScript in one go.
22
Swagger contracts, GraphQL or JSON schemas to be included in
your project.
During the migration, refrain from code refactoring and focus only
on adding types to your modules.
The fifth step is to enable “noImplicitAny,” which will enforce that all
types are known and defined, providing a better TypeScript
experience for your project.
During the migration, you can use the @ts-check directive, which
enables TypeScript type checking in a JavaScript file. This directive
provides a loose version of type checking and can be initially used to
identify issues in JavaScript files. When @ts-check is included in a
file, TypeScript will try to deduce definitions using JSDoc-style
comments. However, consider using JSDoc annotations only at a
very early stage of the migration.
23
more. It is primarily used by integrated development environments
(IDEs) to provide IntelliSense support. It seamlessly integrates with
Visual Studio Code and is utilized by tools like Conquer of
Completion (Coc).
For more information and quick start guides, you can refer to the
official TypeScript Wiki on GitHub:
https://github.com/microsoft/TypeScript/wiki/
Structural Typing
TypeScript is based on a structural type system. This means that the
compatibility and equivalence of types are determined by the type’s
actual structure or definition, rather than its name or place of
declaration, as in nominative type systems like C# or C.
24
type X = {
a: string;
};
type Y = {
a: string;
};
const x: X = { a: 'a' };
const y: Y = x; // Valid
A type “X” is compatible with “Y” if “Y” has at least the same
members as “X”.
type X = {
a: string;
};
const y = { a: 'A', b: 'B' }; // Valid, as it has at least the same
members as X
const r: X = y;
25
y = x; // Invalid
x = y; // Invalid
26
The rest parameter is treated as an infinite series of optional
parameters:
type X = (a: number, ...rest: number[]) => undefined;
let x: X = a => undefined; //valid
27
// Bivariance does accept supertypes
console.log(getA(new X('x'))); // Valid
console.log(getA(new Y('Y'))); // Valid
console.log(getA(new Z('z'))); // Valid
Enums are comparable and valid with numbers and vice versa, but
comparing Enum values from different Enum types is invalid.
enum X {
A,
B,
}
enum Y {
A,
B,
C,
}
const xa: number = X.A; // Valid
const ya: Y = 0; // Valid
X.A === Y.A; // Invalid
class Y {
private a: string;
constructor(value: string) {
this.a = value;
}
}
The comparison check does not take into consideration the different
inheritance hierarchy, for instance:
28
class X {
public a: string;
constructor(value: string) {
this.a = value;
}
}
class Y extends X {
public a: string;
constructor(value: string) {
super(value);
this.a = value;
}
}
class Z {
public a: string;
constructor(value: string) {
this.a = value;
}
}
let x: X = new X('x');
let y: Y = new Y('y');
let z: Z = new Z('z');
x === y; // Valid
x === z; // Valid even if z is from a different inheritance
hierarchy
interface X<T> {}
const x: X<number> = 1;
const y: X<string> = 'a';
x === y; // Valid as the type argument is not used in the final
structure
29
When generics do not have their type argument specified, all the
unspecified arguments are treated as types with “any”:
type X = <T>(x: T) => T;
type Y = <K>(y: K) => K;
let x: X = x => x;
let y: Y = y => y;
x = y; // Valid
Remember:
let a: number = 1;
let b: number = 2;
a = b; // Valid, everything is assignable to itself
let c: any;
c = 1; // Valid, all types are assignable to any
let d: unknown;
d = 1; // Valid, all types are assignable to unknown
let e: unknown;
let e1: unknown = e; // Valid, unknown is only assignable to itself
and any
let e2: any = e; // Valid
let e3: number = e; // Invalid
let f: never;
f = 1; // Invalid, nothing is assignable to never
let g: void;
let g1: any;
g = 1; // Invalid, void is not assignable to or from anything
expect any
g = g1; // Valid
30
Types as Sets
In TypeScript, a type is a set of possible values. This set is also
referred to as the domain of the type. Each value of a type can be
viewed as an element in a set. A type establishes the constraints that
every element in the set must satisfy to be considered a member of
that set. The primary task of TypeScript is to check and verify
whether one set is a subset of another.
31
TypeScript Set term Example
‘never’
32
An union, (T1 | T2) creates a wider set (both):
type X = {
a: string;
};
type Y = {
b: string;
};
type XY = X | Y;
const r: XY = { a: 'a', b: 'x' }; // Valid
33
}
const z: Z = { a: 'a', b: 'b', c: 'c' };
interface X1 {
a: string;
}
interface Y1 {
a: string;
b: string;
}
interface Z1 {
a: string;
b: string;
c: string;
}
const z1: Z1 = { a: 'a', b: 'b', c: 'c' };
const r: Z1 = z; // Valid
Type Declaration
In the following example, we use x: X (“: Type”) to declare a type for
the variable x.
type X = {
a: string;
};
// Type declaration
const x: X = {
a: 'a',
};
34
type X = {
a: string;
};
const x: X = {
a: 'a',
b: 'b', // Error: Object literal may only specify known
properties
};
Type Assertion
It is possible to add an assertion by using the as keyword. This tells
the compiler that the developer has more information about a type
and silences any errors that may occur.
For example:
type X = {
a: string;
};
const x = {
a: 'a',
b: 'b',
} as X;
35
HTMLInputElement. Type assertions can also be used to remap
keys, as shown in the example below with template literals:
type J<Type> = {
[Property in keyof Type as `prefix_${string &
Property}`]: () => Type[Property];
};
type X = {
a: string;
b: number;
};
type Y = J<X>;
In this example, the type J<Type> uses a mapped type with a template
literal to remap the keys of Type. It creates new properties with a
“prefix_” added to each key, and their corresponding values are
functions returning the original property values.
Ambient Declarations
Ambient declarations are files that describe types for JavaScript
code, they have a file name format as .d.ts.. They are usually
imported and used to annotate existing JavaScript libraries or to add
types to existing JS files in your project.
For your defined Ambient Declarations, you can import using the
“triple-slash” reference:
36
/// <reference path="./library-types.d.ts" />
You can use Ambient Declarations even within JavaScript files using
// @ts-check.
Weak Types
A type is considered weak when it contains nothing but a set of all-
optional properties:
type X = {
a?: string;
b?: string;
};
37
type Options = {
a?: string;
b?: string;
};
38
parameter, TypeScript will throw an error if the object literal
specifies properties that do not exist in the target type.
let x: X;
x = { a: 'a', b: 'b' }; // Freshness check: Invalid assignment
var y: Y;
y = { a: 'a', bx: 'bx' }; // Freshness check: Invalid assignment
fn(x);
fn(y); // Widening: No errors, structurally type compatible
let c: X = { a: 'a' };
let d: Y = { a: 'a', b: '' };
c = d; // Widening: No Freshness check
Type Inference
TypeScript can infer types when no annotation is provided during:
Variable initialization.
Member initialization.
Setting defaults for parameters.
Function return type.
For example:
let x = 'x'; // The type inferred is string
39
The TypeScript compiler analyzes the value or expression and
determines its type based on the available information.
If the compiler cannot find the best common types, it returns a union
type. For example:
let x = [new RegExp('x'), new Date()]; // Type inferred is: (RegExp
| Date)[]
Type Widening
Type widening is the process in which TypeScript assigns a type to a
variable initialized when no type annotation was provided. It allows
narrow to wider types but not vice versa. In the following example:
let x = 'x'; // TypeScript infers as string, a wide type
let y: 'y' | 'x' = 'y'; // y types is a union of literal types
y = x; // Invalid Type 'string' is not assignable to type '"x" |
"y"'.
40
TypeScript assigns string to x based on the single value provided
during initialization (x), this is an example of widening.
Const
Using the const keyword when declaring a variable results in a
narrower type inference in TypeScript.
For example:
const x = 'x'; // TypeScript infers the type of x as 'x', a
narrower type
let y: 'y' | 'x' = 'y';
y = x; // Valid: The type of x is inferred as 'x'
41
As you can see, the properties a and b are inferred with a type of
string .
Now we can see that the properties a and b are inferred as const, so a
and b are treated as string literals rather than just string types.
Const assertion
On a single property:
const v = {
x: 3 as const,
};
v.x = 3;
On an entire object:
const v = {
x: 1,
y: 2,
} as const;
This can be particularly useful when defining the type for a tuple:
const x = [1, 2, 3]; // number[]
const y = [1, 2, 3] as const; // Tuple of readonly [1, 2, 3]
42
Explicit Type Annotation
We can be specific and pass a type, in the following example property
x is of type number:
const v = {
x: 1, // Inferred type: number (widening)
};
v.x = 3; // Valid
Type Narrowing
Type Narrowing is the process in TypeScript where a general type is
narrowed down to a more specific type. This occurs when TypeScript
analyzes the code and determines that certain conditions or
operations can refine the type information.
Conditions
By using conditional statements, such as if or switch, TypeScript can
narrow down the type based on the outcome of the condition. For
example:
let x: number | undefined = 10;
if (x !== undefined) {
x += 100; // The type is number, which had been narrowed by the
43
condition
}
Throwing or returning
Throwing an error or returning early from a branch can be used to
help TypeScript narrow down a type. For example:
let x: number | undefined = 10;
if (x === undefined) {
throw 'error';
}
x += 100;
Discriminated Union
44
case 'type_b':
return input.value + 'extra'; // type is B
}
};
const isValid = (item: string | null): item is string => item !==
null; // Custom type guard
Primitive Types
TypeScript supports 7 primitive types. A primitive data type refers to
a type that is not an object and does not have any methods associated
with it. In TypeScript, all primitive types are immutable, meaning
their values cannot be changed once they are assigned.
string
The string primitive type stores textual data, and the value is always
double or single-quoted.
const x: string = 'x';
const y: string = 'y';
45
Strings can span multiple lines if surrounded by the backtick (`)
character:
let sentence: string = `xxx,
yyy`;
boolean
The boolean data type in TypeScript stores a binary value, either true
or false.
const isReady: boolean = true;
number
A number data type in TypeScript is represented with a 64-bit floating
point value. A number type can represent integers and fractions.
TypeScript also supports hexadecimal, binary, and octal, for
instance:
const decimal: number = 10;
const hexadecimal: number = 0xa00d; // Hexadecimal starts with 0x
const binary: number = 0b1010; // Binary starts with 0b
const octal: number = 0o633; // Octal starts with 0o
bigInt
A bigInt represents numeric values that are very large (253 – 1) and
cannot be represented with a number.
46
Notes:
Symbol
Symbols are unique identifiers that can be used as property keys in
objects to prevent naming conflicts.
type Obj = {
[sym: symbol]: number;
};
const a = Symbol('a');
const b = Symbol('b');
let obj: Obj = {};
obj[a] = 123;
obj[b] = 456;
console.log(obj[a]); // 123
console.log(obj[b]); // 456
The null type means that we know that the field does not have a
value, so value is unavailable, it indicates an intentional absence of
value.
47
Array
An array is a data type that can store multiple values of the same type
or not. It can be defined using the following syntax:
const x: string[] = ['a', 'b'];
const y: Array<string> = ['a', 'b'];
const j: Array<string | number> = ['a', 1, 'b', 2]; // Union
any
The any data type represents literally “any” value, it is the default
value when TypeScript cannot infer the type or is not specified.
When using any TypeScript compiler skips the type checking so there
is no type safety when any is being used. Generally do not use any to
silence the compiler when an error occurs, instead focus on fixing the
error as with using any it is possible to break contracts and we lose
the benefits of TypeScript autocomplete.
48
The anytype is usually a source of errors which can mask real
problems with your types. Avoid using it as much as possible.
Type Annotations
On variables declared using var, let and const, it is possible to
optionally add a type:
const x: number = 1;
49
Generally consider annotating type signatures but not the body local
variables and add types always to object literals.
Optional Properties
An object can specify Optional Properties by adding a question mark
? to the end of the property name:
type X = {
a: number;
b?: number; // Optional
};
Readonly Properties
Is it possible to prevent writing on a property by using the modifier
readonlywhich makes sure that the property cannot be re-written but
does not provide any guarantee of total immutability:
interface Y {
readonly a: number;
}
type X = {
readonly a: number;
};
type J = Readonly<{
a: number;
}>;
50
type K = {
readonly [index: number]: string;
};
Index Signatures
In TypeScript we can use as index signature string, number, and
symbol:
type K = {
[name: string | number]: string;
};
const k: K = { x: 'x', 1: 'b' };
console.log(k['x']);
console.log(k[1]);
console.log(k['1']); // Same result as k[1]
Extending Types
It is possible to extend an interface (copy members from another
type):
interface X {
a: string;
}
interface Y extends X {
b: string;
}
51
interface B {
b: string;
}
interface Y extends A, B {
y: string;
}
The extends keyword works only on interfaces and classes, for types
use an intersection:
type A = {
a: number;
};
type B = {
b: number;
};
type C = A & B;
Literal Types
A Literal Type is a single element set from a collective type, it defines
a very exact value that is a JavaScript primitive.
Example of literals:
const a = 'a'; // String literal type
const b = 1; // Numeric literal type
const c = true; // Boolean literal type
52
String, Numeric, and Boolean Literal Types are used in the union,
type guard, and type aliases. In the following example you can see a
type alias union, O can be the only value specified and not any other
string:
type O = 'a' | 'b' | 'c';
Literal Inference
Literal Inference is a feature in TypeScript that allows the type of a
variable or parameter to be inferred based on its value.
In the following example we can see that o.x was inferred as a string
(and not a literal of a) as TypeScript considers that the value can be
changed any time later.
type X = 'a' | 'b';
let o = {
x: 'a', // This is a wider string
};
As you can see the code throws an error when passing o.x to fn as X
is a narrower type.
53
We can solve this issue by using type assertion using const or the X
type:
let o = {
x: 'a' as const,
};
or:
let o = {
x: 'a' as X,
};
strictNullChecks
strictNullChecks is a TypeScript compiler option that enforces strict
null checking. When this option is enabled, variables and parameters
can only be assigned null or undefined if they have been explicitly
declared to be of that type using the union type null | undefined. If a
variable or parameter is not explicitly declared as nullable,
TypeScript will generate an error to prevent potential runtime errors.
Enums
In TypeScript, an enum is a set of named constant values.
enum Color {
Red = '#ff0000',
Green = '#00ff00',
Blue = '#0000ff',
}
54
Numeric enums
In TypeScript, a Numeric Enum is an Enum where each constant is
assigned a numeric value, starting from 0 by default.
enum Size {
Small, // value starts from 0
Medium,
Large,
}
String enums
In TypeScript, a String enum is an Enum where each constant is
assigned a string value.
enum Language {
English = 'EN',
Spanish = 'ES',
}
Constant enums
A constant enum in TypeScript is a special type of Enum where all
the values are known at compile time and are inlined wherever the
enum is used, resulting in more efficient code.
55
const enum Language {
English = 'EN',
Spanish = 'ES',
}
console.log(Language.English);
Reverse mapping
In TypeScript, reverse mappings in Enums refer to the ability to
retrieve the Enum member name from its value. By default, Enum
members have forward mappings from name to value, but reverse
mappings can be created by explicitly setting values for each
member. Reverse mappings are useful when you need to look up an
Enum member by its value, or when you need to iterate over all the
Enum members. Note that only numeric enums members will
generate reverse mappings, while String Enum members do not get a
reverse mapping generated at all.
Compiles to:
56
'use strict';
var Grade;
(function (Grade) {
Grade[(Grade['A'] = 90)] = 'A';
Grade[(Grade['B'] = 80)] = 'B';
Grade[(Grade['C'] = 70)] = 'C';
Grade['F'] = 'fail';
})(Grade || (Grade = {}));
Ambient enums
An ambient enum in TypeScript is a type of Enum that is defined in a
declaration file (*.d.ts) without an associated implementation. It
allows you to define a set of named constants that can be used in a
type-safe way across different files without having to import the
implementation details in each file.
57
whose value is set at compile-time and cannot be changed during
runtime. Computed members are allowed in regular Enums, while
constant members are allowed in both regular and const enums.
// Constant members
enum Color {
Red = 1,
Green = 5,
Blue = Red + Green,
}
console.log(Color.Blue); // 6 generation at compilation time
// Computed members
enum Color {
Red = 1,
Green = Math.pow(2, 2),
Blue = Math.floor(Math.random() * 3) + 1,
}
console.log(Color.Blue); // random number generated at run time
enum E {
A = 2 * 5, // Numeric literal
B = 'bar', // String literal
C = identity(42), // Opaque computed
}
console.log(E.C); //42
58
Narrowing
TypeScript narrowing is the process of refining the type of a variable
within a conditional block. This is useful when working with union
types, where a variable can have more than one type.
Truthiness narrowing
Truthiness narrowing in TypeScript works by checking whether a
variable is truthy or falsy to narrow its type accordingly.
const toUpperCase = (name: string | null) => {
if (name) {
return name.toUpperCase();
} else {
return null;
}
};
59
Equality narrowing
Equality narrowing in TypeScript works by checking whether a
variable is equal to a specific value or not, to narrow its type
accordingly.
In Operator narrowing
The in Operator narrowing in TypeScript is a way to narrow the type
of a variable based on whether a property exists within the variable’s
type.
type Dog = {
name: string;
breed: string;
};
type Cat = {
name: string;
likesCream: boolean;
};
60
}
};
instanceof narrowing
The instanceof operator narrowing in TypeScript is a way to narrow
the type of a variable based on its constructor function, by checking if
an object is an instance of a certain class or interface.
class Square {
constructor(public width: number) {}
}
class Rectangle {
constructor(
public width: number,
public height: number
) {}
}
function area(shape: Square | Rectangle) {
if (shape instanceof Square) {
return shape.width * shape.width;
} else {
return shape.width * shape.height;
}
}
const square = new Square(5);
const rectangle = new Rectangle(5, 10);
console.log(area(square)); // 25
console.log(area(rectangle)); // 50
Assignments
TypeScript narrowing using assignments is a way to narrow the type
of a variable based on the value assigned to it. When a variable is
assigned a value, TypeScript infers its type based on the assigned
value, and it narrows the type of the variable to match the inferred
type.
61
let value: string | number;
value = 'hello';
if (typeof value === 'string') {
console.log(value.toUpperCase());
}
value = 42;
if (typeof value === 'number') {
console.log(value.toFixed(2));
}
For example:
const f1 = (x: unknown) => {
const isString = typeof x === 'string';
if (isString) {
x.length;
}
};
const f2 = (
obj: { kind: 'foo'; foo: string } | { kind: 'bar'; bar: number
}
) => {
const isFoo = obj.kind === 'foo';
if (isFoo) {
obj.foo;
} else {
obj.bar;
62
}
};
const f6 = (
obj: { kind: 'foo'; foo: string } | { kind: 'bar'; bar: number
}
) => {
const isFoo = obj.kind === 'foo';
obj = obj;
if (isFoo) {
obj.foo; // Error, no narrowing because obj is assigned in
function body
}
};
Type Predicates
Type Predicates in TypeScript are functions that return a boolean
value and are used to narrow the type of a variable to a more specific
type.
const isString = (value: unknown): value is string => typeof value
=== 'string';
63
console.log('not a string');
}
};
Discriminated Unions
Discriminated Unions in TypeScript are a type of union type that
uses a common property, known as the discriminant, to narrow
down the set of possible types for the union.
type Square = {
kind: 'square'; // Discriminant
size: number;
};
type Circle = {
kind: 'circle'; // Discriminant
radius: number;
};
console.log(area(square)); // 25
console.log(area(circle)); // 12.566370614359172
64
The never Type
When a variable is narrowed to a type that cannot contain any
values, the TypeScript compiler will infer that the variable must be of
the never type. This is because The never Type represents a value that
can never be produced.
const printValue = (val: string | number) => {
if (typeof val === 'string') {
console.log(val.toUpperCase());
} else if (typeof val === 'number') {
console.log(val.toFixed(2));
} else {
// val has type never here because it can never be anything
other than a string or a number
const neverVal: never = val;
console.log(`Unexpected value: ${neverVal}`);
}
};
Exhaustiveness checking
Exhaustiveness checking is a feature in TypeScript that ensures all
possible cases of a discriminated union are handled in a switch
statement or an if statement.
type Direction = 'up' | 'down';
65
be executed
}
};
The never type is used to ensure that the default case is exhaustive
and that TypeScript will raise an error if a new value is added to the
Direction type without being handled in the switch statement.
Object Types
In TypeScript, object types describe the shape of an object. They
specify the names and types of the object’s properties, as well as
whether those properties are required or optional.
66
const sum = (x: { a: number; b: number }) => x.a + x.b;
console.log(sum({ a: 5, b: 1 }));
67
types, and you want to ensure that the length and types of the tuple
cannot be changed inadvertently.
const x = [10, 'hello'] as const;
x.push(2); // Error
Union Type
A Union Type is a type that represents a value that can be one of
several types. Union Types are denoted using the | symbol between
each possible type.
let x: string | number;
x = 'hello'; // Valid
x = 123; // Valid
68
Intersection Types
An Intersection Type is a type that represents a value that has all the
properties of two or more types. Intersection Types are denoted
using the & symbol between each type.
type X = {
a: string;
};
type Y = {
b: string;
};
const j: J = {
a: 'a',
b: 'b',
};
Type Indexing
Type indexing refers to the ability to define types that can be indexed
by a key that is not known in advance, using an index signature to
specify the type for properties that are not explicitly declared.
type Dictionary<T> = {
[key: string]: T;
};
const myDict: Dictionary<string> = { a: 'a', b: 'b' };
console.log(myDict['a']); // Returns a
69
Type from Value
Type from Value in TypeScript refers to the automatic inference of a
type from a value or expression through type inference.
const x = 'x'; // TypeScript can automatically infer that the type
of the message variable is string
70
Mapped Types
Mapped Types in TypeScript allow you to create new types based on
an existing type by transforming each property using a mapping
function. By mapping existing types, you can create new types that
represent the same information in a different format. To create a
mapped type, you access the properties of an existing type using the
keyof operator and then alter them to produce a new type. In the
following example:
type MyMappedType<T> = {
[P in keyof T]: T[P][];
};
type MyType = {
foo: string;
bar: number;
};
type MyNewType = MyMappedType<MyType>;
const x: MyNewType = {
foo: ['hello', 'world'],
bar: [1, 2, 3],
};
71
?: This designates a property in the mapped type as optional.
Examples:
type ReadOnly<T> = { readonly [P in keyof T]: T[P] }; // All
properties marked as read-only
Conditional Types
Conditional Types are a way to create a type that depends on a
condition, where the type to be created is determined based on the
result of the condition. They are defined using the extends keyword
and a ternary operator to conditionally choose between two types.
type IsArray<T> = T extends any[] ? true : false;
72
type NullableNumberOrBool = Nullable<NumberOrBool>; // number |
boolean | null
Extract<Type, Union>: This type extracts all the types from Union that
are assignable to Type.
73
Partial<Type>: This type makes all properties in Type optional.
Any type
The any type is a special type (universal supertype) that can be used
to represent any type of value (primitives, objects, arrays, functions,
errors, symbols). It is often used in situations where the type of a
value is not known at compile time, or when working with values
from external APIs or libraries that do not have TypeScript typings.
Limit the usage of any to specific cases where the type is truly
unknown.
Do not return any types from a function as you will lose type
safety in the code using that function weakening your type safety.
Instead of any use @ts-ignore if you need to silence the compiler.
let value: any;
value = true; // Valid
value = 7; // Valid
74
Unknown type
In TypeScript, the unknown type represents a value that is of an
unknown type. Unlike any type, which allows for any type of value,
unknown requires a type check or assertion before it can be used in a
specific way so no operations are permitted on an unknown without
first asserting or narrowing to a more specific type.
The unknown type is only assignable to any type and the unknown type
itself, it is a type-safe alternative to any.
let value: unknown;
Void type
The void type is used to indicate that a function does not return a
value.
const sayHello = (): void => {
console.log('Hello!');
};
75
Never type
The never type represents values that never occur. It is used to denote
functions or expressions that never return or throw an error.
Throwing an error:
const throwError = (message: string): never => {
throw new Error(message);
};
The never type is useful in ensuring type safety and catching potential
errors in your code. It helps TypeScript analyze and infer more
precise types when used in combination with other types and control
flow statements, for instance:
type Direction = 'up' | 'down';
const move = (direction: Direction): void => {
switch (direction) {
case 'up':
// move up
break;
case 'down':
// move down
break;
default:
const exhaustiveCheck: never = direction;
throw new Error(`Unhandled direction:
${exhaustiveCheck}`);
}
};
76
Interface and Type
Common Syntax
In TypeScript, interfaces define the structure of objects, specifying
the names and types of properties or methods that an object must
have. The common syntax for defining an interface in TypeScript is
as follows:
interface InterfaceName {
property1: Type1;
// ...
method1(arg1: ArgType1, arg2: ArgType2): ReturnType;
// ...
}
Example interface:
interface Person {
name: string;
77
age: number;
greet(): void;
}
Example of type:
type TypeName = {
property1: string;
method1(arg1: string, arg2: string): string;
};
In TypeScript, types are used to define the shape of data and enforce
type checking. There are several common syntaxes for defining types
in TypeScript, depending on the specific use case. Here are some
examples:
Basic Types
let myNumber: number = 123; // number type
let myBoolean: boolean = true; // boolean type
let myArray: string[] = ['a', 'b']; // array of strings
let myTuple: [string, number] = ['a', 123]; // tuple
78
Built-in Type Primitives
TypeScript has several built-in type primitives that can be used to
define variables, function parameters, and return types:
79
Function
Object
Boolean
Error
Number
BigInt
Math
Date
String
RegExp
Array
Map
Set
Promise
Intl
Overloads
Function overloads in TypeScript allow you to define multiple
function signatures for a single function name, enabling you to
define functions that can be called in multiple ways. Here’s an
example:
// Overloads
function sayHi(name: string): string;
function sayHi(names: string[]): string[];
// Implementation
function sayHi(name: unknown): unknown {
if (typeof name === 'string') {
return `Hi, ${name}!`;
} else if (Array.isArray(name)) {
return name.map(name => `Hi, ${name}!`);
}
throw new Error('Invalid value');
}
80
sayHi('xx'); // Valid
sayHi(['aa', 'bb']); // Valid
constructor(message: string) {
this.message = message;
}
// overload
sayHi(name: string): string;
sayHi(names: string[]): ReadonlyArray<string>;
// implementation
sayHi(name: unknown): unknown {
if (typeof name === 'string') {
return `${this.message}, ${name}!`;
} else if (Array.isArray(name)) {
return name.map(name => `${this.message}, ${name}!`);
}
throw new Error('value is invalid');
}
}
console.log(new Greeter('Hello').sayHi('Simon'));
81
interface X {
b: number;
}
const person: X = {
a: 'a',
b: 7,
};
82
methods. On the other hand, types do not support declaration
merging. This can be helpful when you want to add extra
functionality or customize existing types without modifying the
original definitions or patching missing or incorrect types.
interface A {
x: string;
}
interface A {
y: string;
}
const j: A = {
x: 'xx',
y: 'yy',
};
Both types and interfaces can extend other types/interfaces, but the
syntax is different. With interfaces, you use the extends keyword to
inherit properties and methods from other interfaces. However, an
interface cannot extend a complex type like a union type.
interface A {
x: string;
y: number;
}
interface B extends A {
z: string;
}
const car: B = {
x: 'x',
y: 123,
z: 'z',
};
For types, you use the & operator to combine multiple types into a
single type (intersection).
interface A {
x: string;
y: number;
83
}
type B = A & {
j: string;
};
const c: B = {
x: 'x',
y: 123,
j: 'j',
};
type Person = {
name: string;
age: number;
};
type Employee = {
id: number;
department: Department;
};
84
type C = A | B; // Union of interfaces
Class
The class has two private properties: name of type string and age of
type number.
The class has a public method named sayHi that logs a greeting
message.
85
To create an instance of a class in TypeScript, you can use the new
keyword followed by the class name, followed by parentheses (). For
instance:
const myObject = new Person('John Doe', 25);
myObject.sayHi(); // Output: Hello, my name is John Doe and I am 25
years old.
Constructor
Constructors are special methods within a class that are used to
initialize the object’s properties when an instance of the class is
created.
class Person {
public name: string;
public age: number;
sayHello() {
console.log(
`Hello, my name is ${this.name} and I'm ${this.age}
years old.`
);
}
}
class Person {
name: string;
age: number;
86
sex: Sex;
constructor();
constructor(name: string);
constructor(name: string, age: number);
constructor(name?: string, age?: number) {
this.name = name ?? 'Unknown';
this.age = age ?? 0;
}
displayInfo() {
console.log(`Name: ${this.name}, Age: ${this.age}`);
}
}
87
Private and Protected Constructors
In TypeScript, constructors can be marked as private or protected,
which restricts their accessibility and usage.
constructor(value: number) {
super();
this.value = value;
}
}
Access Modifiers
Access Modifiers private, protected, and public are used to control
the visibility and accessibility of class members, such as properties
88
and methods, in TypeScript classes. These modifiers are essential for
enforcing encapsulation and establishing boundaries for accessing
and modifying the internal state of a class.
The private modifier restricts access to the class member only within
the containing class.
The protected modifier allows access to the class member within the
containing class and its derived classes.
constructor(value: string) {
this._myProperty = value;
}
get myProperty(): string {
return this._myProperty;
}
set myProperty(value: string) {
this._myProperty = value;
}
}
89
Auto-Accessors in Classes
TypeScript version 4.9 adds support for auto-accessors, a
forthcoming ECMAScript feature. They resemble class properties but
are declared with the “accessor” keyword.
class Animal {
accessor name: string;
constructor(name: string) {
this.name = name;
}
}
get name() {
return this.#__name;
}
set name(value: string) {
this.#__name = name;
}
constructor(name: string) {
this.name = name;
}
}
this
In TypeScript, the this keyword refers to the current instance of a
class within its methods or constructors. It allows you to access and
modify the properties and methods of the class from within its own
scope. It provides a way to access and manipulate the internal state
of an object within its own methods.
90
class Person {
private name: string;
constructor(name: string) {
this.name = name;
}
public introduce(): void {
console.log(`Hello, my name is ${this.name}.`);
}
}
Parameter Properties
Parameter properties allow you to declare and initialize class
properties directly within the constructor parameters avoiding
boilerplate code, example:
class Person {
constructor(
private name: string,
public age: number
) {
// The "private" and "public" keywords in the constructor
// automatically declare and initialize the corresponding
class properties.
}
public introduce(): void {
console.log(
`Hello, my name is ${this.name} and I am ${this.age}
years old.`
);
}
}
const person = new Person('Alice', 25);
person.introduce();
91
Abstract Classes
Abstract Classes are used in TypeScript mainly for inheritance, they
provide a way to define common properties and methods that can be
inherited by subclasses. This is useful when you want to define
common behavior and enforce that subclasses implement certain
methods. They provide a way to create a hierarchy of classes where
the abstract base class provides a shared interface and common
functionality for the subclasses.
abstract class Animal {
protected name: string;
constructor(name: string) {
this.name = name;
}
With Generics
Classes with generics allow you to define reusable classes which can
work with different types.
class Container<T> {
private item: T;
constructor(item: T) {
this.item = item;
}
92
getItem(): T {
return this.item;
}
Decorators
Decorators provide a mechanism to add metadata, modify behavior,
validate, or extend the functionality of the target element. They are
functions that execute at runtime. Multiple decorators can be applied
to a declaration.
93
Logging.
Authorization and authentication.
Error guarding.
Types of decorators:
Class Decorators
@toString
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
greet() {
return 'Hello, ' + this.name;
}
94
}
const person = new Person('Simon');
/* Logs:
{"name":"Simon"}
{"kind":"class","name":"Person"}
*/
Property Decorator
class MyClass {
@upperCase
prop1 = 'hello!';
}
Method Decorator
Method decorators allow you to change or enhance the behavior of
methods. Below is an example of a simple logger:
function log<This, Args extends any[], Return>(
target: (this: This, ...args: Args) => Return,
context: ClassMethodDecoratorContext<
This,
(this: This, ...args: Args) => Return
>
) {
95
const methodName = String(context.name);
return replacementMethod;
}
class MyClass {
@log
sayHello() {
console.log('Hello!');
}
}
new MyClass().sayHello();
It logs:
LOG: Entering method 'sayHello'.
Hello!
LOG: Exiting method 'sayHello'.
96
if (value < min || value > max) {
throw 'Invalid';
}
Object.defineProperty(this, context.name, {
value,
enumerable: true,
});
return value;
};
};
}
class MyClass {
private _value = 0;
constructor(value: number) {
this._value = value;
}
@range(1, 100)
get getValue(): number {
return this._value;
}
}
Decorator Metadata
Decorator Metadata simplifies the process for decorators to apply
and utilize metadata in any class. They can access a new metadata
property on the context object, which can serve as a key for both
primitives and objects. Metadata information can be accessed on the
class via Symbol.metadata.
97
//@ts-ignore
Symbol.metadata ??= Symbol('Symbol.metadata'); // Simple polify
type Context =
| ClassFieldDecoratorContext
| ClassAccessorDecoratorContext
| ClassMethodDecoratorContext; // Context contains property
metadata: DecoratorMetadata
class MyClass {
@setMetadata
a = 123;
@setMetadata
accessor b = 'b';
@setMetadata
fn() {}
}
console.log(JSON.stringify(metadata)); //
{"bar":true,"baz":true,"foo":true}
Inheritance
Inheritance refers to the mechanism by which a class can inherit
properties and methods from another class, known as the base class
or superclass. The derived class, also called the child class or
subclass, can extend and specialize the functionality of the base class
by adding new properties and methods or overriding existing ones.
class Animal {
name: string;
98
constructor(name: string) {
this.name = name;
}
speak(): void {
console.log('The animal makes a sound');
}
}
speak(): void {
console.log('Woof! Woof!');
}
}
interface Swimmable {
swim(): void;
99
}
swim() {
console.log('Swimming...');
}
}
Statics
TypeScript has static members. To access the static members of a
class, you can use the class name followed by a dot, without the need
to create an object.
class OfficeWorker {
static memberCount: number = 0;
100
const total = OfficeWorker.memberCount;
console.log(total); // 2
Property initialization
There are several ways how you can initialize properties for a class in
TypeScript:
Inline:
In the constructor:
class MyClass {
property1: string;
property2: number;
constructor() {
this.property1 = 'default value';
this.property2 = 42;
}
}
101
console.log(this.property2);
}
}
const x = new MyClass();
x.log();
Method overloading
Method overloading allows a class to have multiple methods with the
same name but different parameter types or a different number of
parameters. This allows us to call a method in different ways based
on the arguments passed.
class MyClass {
add(a: number, b: number): number; // Overload signature 1
add(a: string, b: string): string; // Overload signature 2
Generics
Generics allow you to create reusable components and functions that
can work with multiple types. With generics, you can parameterize
types, functions, and interfaces, allowing them to operate on
different types without explicitly specifying them beforehand.
102
Generics allow you to make code more flexible and reusable.
Generic Type
To define a generic type, you use angle brackets (<>) to specify the
type parameters, for instance:
function identity<T>(arg: T): T {
return arg;
}
const a = identity('x');
const b = identity(123);
Generic Classes
Generics can be applied also to classes, in this way they can work
with multiple types by using type parameters. This is useful to create
reusable class definitions that can operate on different data types
while maintaining type safety.
class Container<T> {
private item: T;
constructor(item: T) {
this.item = item;
}
getItem(): T {
return this.item;
}
}
103
const stringContainer = new Container<string>('hello');
console.log(stringContainer.getItem()); // hello
Generic Constraints
Generic parameters can be constrained using the extends keyword
followed by a type or interface that the type parameter must satisfy.
printLen('Hello'); // 5
printLen([1, 2, 3]); // 3
printLen({ length: 10 }); // 10
printLen(123); // Invalid
104
Generic contextual narrowing
Contextual narrowing for generics is the mechanism in TypeScript
that allows the compiler to narrow down the type of a generic
parameter based on the context in which it is used, it is useful when
working with generic types in conditional statements:
function process<T>(value: T): void {
if (typeof value === 'string') {
// Value is narrowed down to type 'string'
console.log(value.length);
} else if (typeof value === 'number') {
// Value is narrowed down to type 'number'
console.log(value.toFixed(2));
}
}
process('hello'); // 5
process(3.14159); // 3.14
const obj = {
prop2: 123,
prop1: 'Origin',
105
};
log(obj); // Valid
Namespacing
In TypeScript, namespaces are used to organize code into logical
containers, preventing naming collisions and providing a way to
group related code together. The usage of the export keywords allows
access to the namespace in “outside” modules.
export namespace MyNamespace {
export interface MyInterface1 {
prop1: boolean;
}
export interface MyInterface2 {
prop2: string;
}
}
const a: MyNamespace.MyInterface1 = {
prop1: true,
};
Symbols
Symbols are a primitive data type that represents an immutable
value which is guaranteed to be globally unique throughout the
lifetime of the program.
Symbols can be used as keys for object properties and provide a way
to create non-enumerable properties.
const key1: symbol = Symbol('key1');
const key2: symbol = Symbol('key2');
const obj = {
[key1]: 'value 1',
106
[key2]: 'value 2',
};
console.log(obj[key1]); // value 1
console.log(obj[key2]); // value 2
Triple-Slash Directives
Triple-slash directives are special comments that provide
instructions to the compiler about how to process a file. These
directives begin with three consecutive slashes (///) and are typically
placed at the top of a TypeScript file and have no effects on the
runtime behavior.
107
Type Manipulation
Mapped Types:
108
Conditional types:
Utility Types
Several built-in utility types can be used to manipulate types, below a
list of the most common used:
Awaited<T>
Constructs a type recursively unwrap Promises.
type A = Awaited<Promise<string>>; // string
109
Partial<T>
Constructs a type with all properties of T set to optional.
type Person = {
name: string;
age: number;
};
Required<T>
Constructs a type with all properties of T set to required.
type Person = {
name?: string;
age?: number;
};
Readonly<T>
Constructs a type with all properties of T set to readonly.
type Person = {
name: string;
age: number;
};
type A = Readonly<Person>;
Record<K, T>
Constructs a type with a set of properties K of type T.
110
type Product = {
name: string;
price: number;
};
Pick<T, K>
Constructs a type by picking the specified properties K from T.
type Product = {
name: string;
price: number;
};
Omit<T, K>
Exclude<T, U>
111
Extract<T, U>
Constructs a type by extracting all values of type U from T.
type Union = 'a' | 'b' | 'c';
type MyType = Extract<Union, 'a' | 'c'>; // a | c
NonNullable<T>
Parameters<T>
ConstructorParameters<T>
112
ReturnType<T>
Extracts the return type of a function type T.
type Func = (name: string) => number;
type MyType = ReturnType<Func>; // number
InstanceType<T>
Extracts the instance type of a class type T.
class Person {
name: string;
constructor(name: string) {
this.name = name;
}
sayHello() {
console.log(`Hello, my name is ${this.name}!`);
}
}
ThisParameterType<T>
113
OmitThisParameter<T>
Removes the ‘this’ parameter from a function type T.
function capitalize(this: String) {
return this[0].toUpperCase + this.substring(1).toLowerCase();
}
ThisType<T>
Servers as a market for a contextual this type.
type Logger = {
log: (error: string) => void;
};
Uppercase<T>
Make uppercase the name of the input type T.
type MyType = Uppercase<'abc'>; // "ABC"
Lowercase<T>
Make lowercase the name of the input type T.
type MyType = Lowercase<'ABC'>; // "abc"
114
Capitalize<T>
Capitalize the name of the input type T.
type MyType = Capitalize<'abc'>; // "Abc"
Uncapitalize<T>
Uncapitalize the name of the input type T.
type MyType = Uncapitalize<'Abc'>; // "abc"
NoInfer<T>
NoInfer is a utility type designed to block the automatic inference of
types within the scope of a generic function.
Example:
// Automatic inference of types within the scope of a generic
function.
function fn<T extends string>(x: T[], y: T) {
return x.concat(y);
}
const r = fn(['a', 'b'], 'c'); // Type here is ("a" | "b" | "c")[]
With NoInfer:
// Example function that uses NoInfer to prevent type inference
function fn2<T extends string>(x: T[], y: NoInfer<T>) {
return x.concat(y);
}
115
Others
Try-Catch-Finally Blocks:
try {
// Code that might throw an error
} catch (error) {
// Handle the error
} finally {
// Code that always executes, finally is optional
}
116
}
}
Mixin classes
Mixin classes allow you to combine and compose behavior from
multiple classes into a single class. They provide a way to reuse and
extend functionality without the need for deep inheritance chains.
abstract class Identifiable {
name: string = '';
logId() {
console.log('id:', this.name);
}
}
abstract class Selectable {
selected: boolean = false;
select() {
this.selected = true;
console.log('Select');
}
deselect() {
this.selected = false;
console.log('Deselect');
}
}
class MyClass {
constructor() {}
}
117
baseCtor.prototype,
name
);
if (descriptor) {
Object.defineProperty(source.prototype, name,
descriptor);
}
});
});
}
Promises:
Async/await:
118
To learn more: https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Statements/async_function
https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Operators/await
WebSocket: https://developer.mozilla.org/en-
US/docs/Web/API/WebSockets_API
constructor(
private start: number,
private end: number
) {
this.current = start;
}
119
public next(): IteratorResult<number> {
if (this.current <= this.end) {
const value = this.current;
this.current++;
return { value, done: false };
} else {
return { value: undefined, done: true };
}
}
[Symbol.iterator](): Iterator<number> {
return this;
}
}
Example:
function* numberGenerator(start: number, end: number):
Generator<number> {
for (let i = start; i <= end; i++) {
yield i;
}
}
120
console.log(num);
}
To learn more:
https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Generator
https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Iterator
Example:
/**
* Computes the power of a given number
* @constructor
* @param {number} base – The base value of the expression
* @param {number} exponent – The exponent value of the expression
*/
function power(base: number, exponent: number) {
return Math.pow(base, exponent);
}
power(10, 2); // function power(base: number, exponent: number):
number
121
https://www.typescriptlang.org/docs/handbook/declaration-
files/dts-from-js.html
@types
Packages under the @types organization are special package naming
conventions used to provide type definitions for existing JavaScript
libraries or modules. For instance using:
npm install --save-dev @types/lodash
JSX
JSX (JavaScript XML) is an extension to the JavaScript language
syntax that allows you to write HTML-like code within your
JavaScript or TypeScript files. It is commonly used in React to define
the HTML structure.
To use JSX you need to set the jsx compiler option in your
tsconfig.json file. Two common configuration options:
“preserve”: emit .jsx files with the JSX unchanged. This option
tells TypeScript to keep the JSX syntax as-is and not transform it
during the compilation process. You can use this option if you
have a separate tool, like Babel, that handles the transformation.
122
“react”: enables TypeScript’s built-in JSX transformation.
React.createElement will be used.
ES6 Modules
TypeScript does support ES6 (ECMAScript 2015) and many
subsequent versions. This means you can use ES6 syntax, such as
arrow functions, template literals, classes, modules, destructuring,
and more.
To enable ES6 features in your project, you can specify the target
property in the tsconfig.json.
A configuration example:
{
"compilerOptions": {
"target": "es6",
"module": "es6",
"moduleResolution": "node",
"sourceMap": true,
"outDir": "dist"
},
"include": ["src"]
}
123
console.log(2 ** (2 ** 2)); // 16
(async () => {
for await (const num of asyncNumbers()) {
console.log(num);
}
})();
124
const parentX = new Parent(); // [Function: Parent]
const child = new Child(); // [Function: Child]
renderWidget();
“tsc –watch”
This command starts a TypeScript compiler with --watch parameter,
with the ability to automatically recompile TypeScript files whenever
they are modified.
tsc --watch
125
Non-null Assertion Operator
The Non-null Assertion Operator (Postfix !) also called Definite
Assignment Assertions is a TypeScript feature that allows you to
assert that a variable or property is not null or undefined, even if
TypeScript’s static type analysis suggests that it might be. With this
feature it is possible to remove any explicit checking.
type Person = {
name: string;
};
Defaulted declarations
Defaulted declarations are used when a variable or parameter is
assigned a default value. This means that if no value is provided for
that variable or parameter, the default value will be used instead.
function greet(name: string = 'Anonymous'): void {
console.log(`Hello, ${name}!`);
}
greet(); // Hello, Anonymous!
greet('John'); // Hello, John!
Optional Chaining
The optional chaining operator ?. works like the regular dot operator
(.) for accessing properties or methods. However, it gracefully
handles null or undefined values by terminating the expression and
returning undefined, instead of throwing an error.
type Person = {
name: string;
126
age?: number;
address?: {
street?: string;
city?: string;
};
};
console.log(person.address?.city); // undefined
127
Function overloading
Function overloading allows you to define multiple function
signatures for the same function name, each with different
parameter types and return type. When you call an overloaded
function, TypeScript uses the provided arguments to determine the
correct function signature:
function makeGreeting(name: string): string;
function makeGreeting(names: string[]): string[];
makeGreeting('Simon');
makeGreeting(['Simone', 'John']);
Recursive Types
A Recursive Type is a type that can refer to itself. This is useful for
defining data structures that have a hierarchical or recursive
structure (potentially infinite nesting), such as linked lists, trees, and
graphs.
type ListNode<T> = {
data: T;
next: ListNode<T> | undefined;
};
128
Recursive Conditional Types
It is possible to define complex type relationships using logic and
recursion in TypeScript. Let’s break it down in simple terms:
Recursion: means a type definition that refers to itself within its own
definition:
type Json = string | number | boolean | null | Json[] | { [key:
string]: Json };
129
ECMAScript Module Support in Node
Node.js added support for ECMAScript Modules starting from
version 15.3.0, and TypeScript has had ECMAScript Module Support
for Node.js since version 4.7. This support can be enabled by using
the module property with the value nodenext in the tsconfig.json file.
Here’s an example:
{
"compilerOptions": {
"module": "nodenext",
"outDir": "./lib",
"declaration": true
}
}
If you want to use ES modules in your project, you can set the type
property to “module” in your package.json file. This instructs Node.js
to treat the project as an ES module project.
Assertion Functions
In TypeScript, assertion functions are functions that indicate the
verification of a specific condition based on their return value. In
their simplest form, an assert function examines a provided
predicate and raises an error when the predicate evaluates to false.
130
function isNumber(value: unknown): asserts value is number {
if (typeof value !== 'number') {
throw new Error('Not a number');
}
}
A tuple type is an array which has a defined length, and were the type
of each element is known:
type Student = [string, number];
const [name, age]: Student = ['Simone', 20];
131
The term “variadic” means indefinite arity (accept a variable number
of arguments).
A variadic tuple is a tuple type which has all the property as before
but the exact shape is not defined yet:
type Bar<T extends unknown[]> = [boolean, ...T, number];
In the previous code we can see that the tuple shape is defined by the
T generic passed in.
Variadic tuples can accept multiple generics make them very flexible:
type Bar<T extends unknown[], G extends unknown[]> = [...T,
boolean, ...G];
Example:
type Items = readonly unknown[];
132
concat([1, 2, 3], ['4', '5', '6']); // [1, 2, 3, "4", "5", "6"]
Boxed types
Boxed types refer to the wrapper objects that are used to represent
primitive types as objects. These wrapper objects provide additional
functionality and methods that are not available directly on the
primitive values.
Demonstration:
const originalNormalize = String.prototype.normalize;
String.prototype.normalize = function () {
console.log(this, typeof this);
return originalNormalize.call(this);
};
console.log('\u0041'.normalize());
The boxed types are usually not needed. Avoid using boxed types and
instead use type for the primitives, for instance string instead of
String.
133
Covariance and Contravariance in
TypeScript
Covariance and Contravariance are used to describe how
relationships work when dealing with inheritance or assignment of
types.
Example: Let’s say we have a space for all animals and a separate
space just for dogs.
In Covariance, you can put all the dogs in the animals space because
dogs are a type of animal. But you cannot put all the animals in the
dog space because there might be other animals mixed in.
In Contravariance, you cannot put all the animals in the dogs space
because the animals space might contain other animals as well.
However, you can put all the dogs in the animal space because all
dogs are also animals.
// Covariance example
class Animal {
name: string;
134
constructor(name: string) {
this.name = name;
}
}
// Contravariance example
type Feed<in T> = (animal: T) => void;
135
Optional Variance Annotations for Type Parameters
TypeScript from version 4.4 allows index signatures for symbols and
template string patterns.
const uniqueSymbol = Symbol('description');
type MyObject = {
[uniqueSymbol]: string;
[key: MyKeys]: number;
};
136
console.log(obj[uniqueSymbol]); // Unique symbol key
console.log(obj['key-a']); // 123
console.log(obj['key-b']); // 456
137
user2.nickName; // string | string[] | undefined
To use type-only imports, you can use the import type keyword.
For example:
import type { House } from './house.ts';
138
using declaration and Explicit Resource
Management
A using declaration is a block-scoped, immutable binding, similar to
const, used for managing disposable resources. When initialized with
a value, the Symbol.dispose method of that value is recorded and
subsequently executed upon exiting the enclosing block scope.
Notes:
Example:
//@ts-ignore
Symbol.dispose ??= Symbol('Symbol.dispose'); // Simple polify
139
};
console.log(1);
{
using work = doWork(); // Resource is declared
console.log(2);
} // Resource is disposed (e.g., `work[Symbol.dispose]()` is
evaluated)
console.log(3);
140
await using declaration
An await using declaration handles an asynchronously disposable
resource. The value must have a Symbol.asyncDispose method, which
will be awaited at the block’s end.
async function doWorkAsync() {
await using work = doWorkAsync(); // Resource is declared
} // Resource is disposed (e.g., `await work[Symbol.asyncDispose]
()` is evaluated)
//@ts-ignore
Symbol.asyncDispose ??= Symbol('Symbol.asyncDispose'); // Simple
polify
async close() {
console.log('Closing the connection...');
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Connection closed.');
}
}
141
console.log('Doing some work...');
} // Resource is disposed (e.g., `await
connection[Symbol.asyncDispose]()` is evaluated)
doWork();
Import Attributes
TypeScript 5.3’s Import Attributes (labels for imports) tell the
runtime how to handle modules (JSON, etc.). This improves security
by ensuring clear imports and aligns with Content Security Policy
(CSP) for safer resource loading. TypeScript ensures they are valid
but lets the runtime handle their interpretation for specific module
handling.
Example:
import config from './config.json' with { type: 'json' };
142