Typescript En
Typescript En
1
typescript
Table of contents
En - Nightly Builds 6
Declaration files - By Example 8
Declaration files - Consumption 13
Declaration files - Deep Dive 14
Declaration files - Do's and Don'ts 18
Declaration files - Introduction 22
Declaration files - Library Structures 24
Declaration files - Publishing 30
Declaration files - Templates 34
Templates - Global modifying module.d.ts 35
Templates - Global plugin.d.ts 37
Templates - Global.d.ts 43
Templates - Module class.d.ts 46
Templates - Module function.d.ts 48
Templates - Module plugin.d.ts 50
Templates - Module.d.ts 52
Get started - TS for Functional Programmers 58
Get started - TS for JS Programmers 68
Get started - TS for OOPers 74
Get started - TS for the New Programmer 79
Handbook v1 - Basic Types 83
Handbook v1 - Classes 91
Handbook v1 - Functions 102
Handbook v1 - Generics 112
Handbook v1 - Interfaces 119
Handbook v1 - Literal Types 132
Handbook v1 - Unions and Intersections 134
Handbook v2 - Basics 140
Handbook v2 - Classes 149
Handbook v2 - Everyday Types 174
Handbook v2 - Modules 188
Handbook v2 - More on Functions 196
Handbook v2 - Narrowing 212
Handbook v2 - Object Types 226
2
TYPESCRIPT Docs - English
3
typescript
4
TYPESCRIPT Docs - English
5
typescript
A nightly build from the TypeScript's main branch is published by midnight PST to npm. Here is how you
can get it and use it with your tools.
Using npm
npm install -g typescript@next
Sublime Text
Update the Settings - User file with the following:
More information is available at the TypeScript Plugin for Sublime Text installation documentation.
Note: Most changes do not require you to install a new version of the VS TypeScript plugin.
The nightly build currently does not include the full plugin setup, but we are working on publishing an in-
staller on a nightly basis as well.
Also see our wiki page on using a custom language service file.
For VS 2015:
6
Nightly Builds
For VS 2013:
Go to TOC
7
typescript
The purpose of this guide is to teach you how to write a high-quality definition file. This guide is structured
by showing documentation for some API, along with sample usage of that API, and explaining how to write
the corresponding declaration.
The global variable myLib has a function makeGreeting for creating greetings, and a property numb
erOfGreetings indicating the number of greetings made so far.
Code
Declaration
Overloaded Functions
Documentation
The getWidget function accepts a number and returns a Widget, or accepts a string and returns a Widget
array.
Code
Declaration
8
Declaration Reference
When specifying a greeting, you must pass a GreetingSettings object. This object has the following
properties:
Code
greet({
greeting: "hello world",
duration: 4000
});
Declaration
interface GreetingSettings {
greeting: string;
duration?: number;
color?: string;
}
Anywhere a greeting is expected, you can provide a string , a function returning a string , or a Gr
eeter instance.
Code
function getGreeting() {
return "howdy";
}
class MyGreeter extends Greeter {}
9
typescript
greet("hello");
greet(getGreeting);
greet(new MyGreeter());
Declaration
Organizing Types
Documentation
The greeter object can log to a file or display an alert. You can provide LogOptions to .log(...)
and alert options to .alert(...)
Code
Declaration
10
Declaration Reference
Classes
Documentation
You can create a greeter by instantiating the Greeter object, or create a customized greeter by ex-
tending from it.
Code
Declaration
Use declare class to describe a class or class-like object. Classes can have properties and methods as
well as a constructor.
greeting: string;
showGreeting(): void;
}
Global Variables
Documentation
Code
Declaration
Use declare var to declare variables. If the variable is read-only, you can use declare const . You can
also use declare let if the variable is block-scoped.
11
typescript
Global Functions
Documentation
You can call the function greet with a string to show a greeting to the user.
Code
greet("hello, world");
Declaration
Go to TOC
12
Consumption
Downloading
Getting type declarations requires no tools apart from npm.
As an example, getting the declarations for a library like lodash takes nothing more than the following
command
It is worth noting that if the npm package already includes its declaration file as described in Publishing,
downloading the corresponding @types package is not needed.
Consuming
From there you’ll be able to use lodash in your TypeScript code with no fuss. This works for both modules
and global code.
For example, once you’ve npm install -ed your type declarations, you can use imports and write
or if you’re not using modules, you can just use the global variable _ .
Searching
For the most part, type declaration packages should always have the same name as the package name on
npm , but prefixed with @types/ , but if you need, you can check out this Type Search to find the package
for your favorite library.
Note: if the declaration file you are searching for is not present, you can always contribute one back
and help out the next developer looking for it. Please see the DefinitelyTyped contribution guidelines
page for details.
Go to TOC
13
typescript
By reading this guide, you'll have the tools to write complex declaration files that expose a friendly API sur-
face. This guide focuses on module (or UMD) libraries because the options here are more varied.
Key Concepts
You can fully understand how to make any shape of declaration by understanding some key concepts of how
TypeScript works.
Types
If you're reading this guide, you probably already roughly know what a type in TypeScript is. To be more ex-
plicit, though, a type is introduced with:
Values
As with types, you probably already understand what a value is. Values are runtime names that we can ref-
erence in expressions. For example let x = 5; creates a value called x .
Namespaces
Types can exist in namespaces. For example, if we have the declaration let x: A.B.C , we say that the
type C comes from the A.B namespace.
This distinction is subtle and important -- here, A.B is not necessarily a type or a value.
14
Deep Dive
This may seem confusing, but it's actually very convenient as long as we don't excessively overload things.
Let's look at some useful aspects of this combining behavior.
Built-in Combinations
Astute readers will notice that, for example, class appeared in both the type and value lists. The declara-
tion class C { } creates two things: a type C which refers to the instance shape of the class, and a val-
ue C which refers to the constructor function of the class. Enum declarations behave similarly.
User Combinations
Let's say we wrote a module file foo.d.ts :
This works well enough, but we might imagine that SomeType and SomeVar were very closely related such
that you'd like them to have the same name. We can use combining to present these two different objects
(the value and the type) under the same name Bar :
This presents a very good opportunity for destructuring in the consuming code:
Again, we've used Bar as both a type and a value here. Note that we didn't have to declare the Bar value
as being of the Bar type -- they're independent.
15
typescript
Advanced Combinations
Some kinds of declarations can be combined across multiple declarations. For example, class C { } and
interface C { } can co-exist and both contribute properties to the C types.
This is legal as long as it does not create a conflict. A general rule of thumb is that values always conflict
with other values of the same name unless they are declared as namespace s, types will conflict if they are
declared with a type alias declaration ( type s = string ), and namespaces never conflict.
interface Foo {
x: number;
}
// ... elsewhere ...
interface Foo {
y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK
class Foo {
x: number;
}
// ... elsewhere ...
interface Foo {
y: number;
}
let a: Foo = ...;
console.log(a.x + a.y); // OK
Note that we cannot add to type aliases ( type s = string; ) using an interface.
class C {}
// ... elsewhere ...
namespace C {
export let x: number;
}
let y = C.x; // OK
16
Deep Dive
Note that in this example, we added a value to the static side of C (its constructor function). This is be-
cause we added a value, and the container for all values is another value (types are contained by name-
spaces, and namespaces are contained by other namespaces).
class C {}
// ... elsewhere ...
namespace C {
export interface D {}
}
let y: C.D; // OK
In this example, there wasn't a namespace C until we wrote the namespace declaration for it. The mean-
ing C as a namespace doesn't conflict with the value or type meanings of C created by the class.
Finally, we could perform many different merges using namespace declarations. This isn't a particularly re-
alistic example, but shows all sorts of interesting behavior:
namespace X {
export interface Y {}
export class Z {}
}
In this example, the first block creates the following name meanings:
Go to TOC
17
typescript
General Types
Number , String , Boolean , Symbol and Object
❌ Don't ever use the types Number , String , Boolean , Symbol , or Object These types refer to non-
primitive boxed objects that are almost never used appropriately in JavaScript code.
/* WRONG */
function reverse(s: String): String;
/* OK */
function reverse(s: string): string;
Instead of Object , use the non-primitive object type (added in TypeScript 2.2).
Generics
❌ Don't ever have a generic type which doesn't use its type parameter. See more details in TypeScript FAQ
page.
any
❌ Don't use any as a type unless you are in the process of migrating a JavaScript project to TypeScript.
The compiler effectively treats any as "please turn off type checking for this thing". It is similar to putting
an @ts-ignore comment around every usage of the variable. This can be very helpful when you are first
migrating a JavaScript project to TypeScript as you can set the type for stuff you haven't migrated yet as
any , but in a full TypeScript project you are disabling type checking for any parts of your program that use
it.
In cases where you don't know what type you want to accept, or when you want to accept anything because
you will be blindly passing it through without interacting with it, you can use unknown .
Callback Types
Return Types of Callbacks
❌ Don't use the return type any for callbacks whose value will be ignored:
/* WRONG */
function fn(x: () => any) {
x();
}
✅ Do use the return type void for callbacks whose value will be ignored:
18
Do's and Don'ts
/* OK */
function fn(x: () => void) {
x();
}
❔ Why: Using void is safer because it prevents you from accidentally using the return value of x in an
unchecked way:
/* WRONG */
interface Fetcher {
getObject(done: (data: unknown, elapsedTime?: number) => void): void;
}
This has a very specific meaning: the done callback might be invoked with 1 argument or might be invoked
with 2 arguments. The author probably intended to say that the callback might not care about the
elapsedTime parameter, but there's no need to make the parameter optional to accomplish this -- it's al-
ways legal to provide a callback that accepts fewer arguments.
/* OK */
interface Fetcher {
getObject(done: (data: unknown, elapsedTime: number) => void): void;
}
/* WRONG */
declare function beforeAll(action: () => void, timeout?: number): void;
declare function beforeAll(
action: (done: DoneFn) => void,
timeout?: number
): void;
/* OK */
declare function beforeAll(
action: (done: DoneFn) => void,
timeout?: number
): void;
19
typescript
❔ Why: It's always legal for a callback to disregard a parameter, so there's no need for the shorter over-
load. Providing a shorter callback first allows incorrectly-typed functions to be passed in because they match
the first overload.
Function Overloads
Ordering
❌ Don't put more general overloads before more specific overloads:
/* WRONG */
declare function fn(x: unknown): unknown;
declare function fn(x: HTMLElement): number;
declare function fn(x: HTMLDivElement): string;
✅ Do sort overloads by putting the more general signatures after more specific signatures:
/* OK */
declare function fn(x: HTMLDivElement): string;
declare function fn(x: HTMLElement): number;
declare function fn(x: unknown): unknown;
❔ Why: TypeScript chooses the first matching overload when resolving function calls. When an earlier
overload is "more general" than a later one, the later one is effectively hidden and cannot be called.
/* WRONG */
interface Example {
diff(one: string): number;
diff(one: string, two: string): number;
diff(one: string, two: string, three: boolean): number;
}
/* OK */
interface Example {
diff(one: string, two?: string, three?: boolean): number;
}
Note that this collapsing should only occur when all overloads have the same return type.
20
Do's and Don'ts
TypeScript resolves signature compatibility by seeing if any signature of the target can be invoked with the
arguments of the source, and extraneous arguments are allowed. This code, for example, exposes a bug
only when the signature is correctly written using optional parameters:
The second reason is when a consumer uses the "strict null checking" feature of TypeScript. Because un-
specified parameters appear as undefined in JavaScript, it's usually fine to pass an explicit undefined to
a function with optional arguments. This code, for example, should be OK under strict nulls:
var x: Example;
// When written with overloads, incorrectly an error because of passing
'undefined' to 'string'
// When written with optionals, correctly OK
x.diff("something", true ? undefined : "hour");
/* WRONG */
interface Moment {
utcOffset(): number;
utcOffset(b: number): Moment;
utcOffset(b: string): Moment;
}
/* OK */
interface Moment {
utcOffset(): number;
utcOffset(b: number | string): Moment;
}
Note that we didn't make b optional here because the return types of the signatures differ.
❔ Why: This is important for people who are "passing through" a value to your function:
Go to TOC
21
typescript
The Declaration Files section is designed to teach you how to write a high-quality TypeScript Declaration
File. We need to assume basic familiarity with the TypeScript language in order to get started.
If you haven't already, you should read the TypeScript Handbook to familiarize yourself with basic concepts,
especially types and modules.
The most common case for learning how .d.ts files work is that you're typing an npm package with no
types. In that case, you can jump straight to Modules .d.ts.
The Declaration Files section is broken down into the following sections.
Declaration Reference
We are often faced with writing a declaration file when we only have examples of the underlying library to
guide us. The Declaration Reference section shows many common API patterns and how to write declara-
tions for each of them. This guide is aimed at the TypeScript novice who may not yet be familiar with every
language construct in TypeScript.
Library Structures
The Library Structures guide helps you understand common library formats and how to write a proper dec-
laration file for each format. If you're editing an existing file, you probably don't need to read this section.
Authors of new declaration files are strongly encouraged to read this section to properly understand how the
format of the library influences the writing of the declaration file.
In the Template section you'll find a number of declaration files that serve as a useful starting point when
writing a new file. If you already know what your structure is, see the d.ts Template section in the sidebar.
Deep Dive
For seasoned authors interested in the underlying mechanics of how declaration files work, the Deep Dive
section explains many advanced concepts in declaration writing, and shows how to leverage these concepts
to create cleaner and more intuitive declaration files.
Publish to npm
The Publishing section explains how to publish your declaration files to an npm package, and shows how to
manage your dependent packages.
22
Introduction
Go to TOC
23
typescript
Broadly speaking, the way you structure your declaration file depends on how the library is consumed.
There are many ways of offering a library for consumption in JavaScript, and you'll need to write your decla-
ration file to match it. This guide covers how to identify common library patterns, and how to write declara-
tion files which correspond to that pattern.
Each type of major library structuring pattern has a corresponding file in the Templates section. You can
start with these templates to help you get going faster.
Identifying the structure of a library is the first step in writing its declaration file. We'll give hints on how to
identify structure both based on its usage and its code. Depending on the library's documentation and orga-
nization, one might be easier than the other. We recommend using whichever is more comfortable to you.
For example, can you only get it through npm or only from a CDN?
Does it add a global object? Does it use require or import / export statements?
ECMAScript 2015 (also known as ES2015, ECMAScript 6, and ES6), CommonJS, and RequireJS have similar
notions of importing a module. In JavaScript CommonJS (Node.js), for example, you would write
var fs = require("fs");
You'll typically see modular libraries include one of these lines in their documentation:
24
Library Structures
or
});
As with global modules, you might see these examples in the documentation of a UMD module, so be sure
to check the code or documentation.
You should first read module.d.ts for an overview on the way they all work.
Then use the template module-function.d.ts if your module can be called like a function:
const x = require("foo");
// Note: calling 'x' as a function
const y = x(42);
Use the template module-class.d.ts if your module can be constructed using new :
const x = require("bar");
// Note: using 'new' operator on the imported variable
const y = new x("hello");
If you have a module which when imported, makes changes to other modules use template module-
plugin.d.ts :
25
typescript
Global Libraries
A global library is one that can be accessed from the global scope (i.e. without using any form of import ).
Many libraries simply expose one or more global variables for use. For example, if you were using jQuery,
the $ variable can be used by simply referring to it:
$(() => {
console.log("hello!");
});
You'll usually see guidance in the documentation of a global library of how to use the library in an HTML
script tag:
<script src="http://a.great.cdn.for/someLib.js"></script>
Today, most popular globally-accessible libraries are actually written as UMD libraries (see below). UMD li-
brary documentation is hard to distinguish from global library documentation. Before writing a global decla-
ration file, make sure the library isn't actually UMD.
function createGreeting(s) {
return "Hello, " + s;
}
or like this:
// Web
window.createGreeting = function (s) {
return "Hello, " + s;
};
// Node
global.createGreeting = function (s) {
return "Hello, " + s;
};
26
Library Structures
UMD
A UMD module is one that can either be used as module (through an import), or as a global (when run in an
environment without a module loader). Many popular libraries, such as Moment.js, are written this way. For
example, in Node.js or using RequireJS, you would write:
console.log(moment.format());
If you see tests for typeof define , typeof window , or typeof module in the code of a library, especial-
ly at the top of the file, it's almost always a UMD library.
Documentation for UMD libraries will also often demonstrate a "Using in Node.js" example showing re‐
quire , and a "Using in the browser" example showing using a <script> tag to load the script.
27
typescript
Template
Use the module-plugin.d.ts template.
Consuming Dependencies
There are several kinds of dependencies your library might have. This section shows how to import them
into the declaration file.
Dependencies on Modules
If your library depends on a module, use an import statement:
28
Library Structures
Footnotes
Preventing Name Conflicts
Note that it's possible to define many types in the global scope when writing a global declaration file. We
strongly discourage this as it leads to possible unresolvable name conflicts when many declaration files are
in a project.
A simple rule to follow is to only declare types namespaced by whatever global variable the library defines.
For example, if the library defines the global value 'cats', you should write
But not
// at top-level
interface CatsKittySettings {}
This guidance also ensures that the library can be transitioned to UMD without breaking declaration file
users.
In ES6-compliant module loaders, the top-level object (here imported as exp ) can only have properties;
the top-level module object can never be callable.
The most common solution here is to define a default export for a callable/constructable object; module
loaders commonly detect this situation automatically and replace the top-level object with the default ex-
port. TypeScript can handle this for you, if you have "esModuleInterop": true in your tsconfig.json.
Go to TOC
29
typescript
Now that you have authored a declaration file following the steps of this guide, it is time to publish it to
npm. There are two main ways you can publish your declaration files to npm:
If your types are generated by your source code, publish the types with your source code. Both TypeScript
and JavaScript projects can generate types via declaration .
Otherwise, we recommend submitting the types to DefinitelyTyped, which will publish them to the @types
organization on npm.
{
"name": "awesome",
"author": "Vandelay Industries",
"version": "1.0.0",
"main": "./lib/main.js",
"types": "./lib/main.d.ts"
}
Note that the "typings" field is synonymous with types , and could be used as well.
Also note that if your main declaration file is named index.d.ts and lives at the root of the package (next
to index.js ) you do not need to mark the types property, though it is advisable to do so.
Dependencies
All dependencies are managed by npm. Make sure all the declaration packages you depend on are marked
appropriately in the "dependencies" section in your package.json . For example, imagine we authored a
package that used Browserify and TypeScript.
{
"name": "browserify-typescript-extension",
"author": "Vandelay Industries",
"version": "1.0.0",
"main": "./lib/main.js",
"types": "./lib/main.d.ts",
"dependencies": {
"browserify": "latest",
"@types/browserify": "latest",
"typescript": "next"
}
}
30
Publishing
Here, our package depends on the browserify and typescript packages. browserify does not bundle
its declaration files with its npm packages, so we needed to depend on @types/browserify for its declara-
tions. typescript , on the other hand, packages its declaration files, so there was no need for any addi-
tional dependencies.
Our package exposes declarations from each of those, so any user of our browserify-typescript-exten‐
sion package needs to have these dependencies as well. For that reason, we used "dependencies" and
not "devDependencies" , because otherwise our consumers would have needed to manually install those
packages. If we had just written a command line application and not expected our package to be used as a
library, we might have used devDependencies .
Red flags
/// <reference path="..." />
Don't use /// <reference path="..." /> in your declaration files.
Make sure to revisit the Consuming dependencies section for more information.
{
"name": "package-name",
"version": "1.0.0",
"types": "./index.d.ts",
"typesVersions": {
31
typescript
This package.json tells TypeScript to first check the current version of TypeScript. If it's 3.1 or later,
TypeScript figures out the path you've imported relative to the package, and reads from the package's
ts3.1 folder.
That's what that { "*": ["ts3.1/*"] } means - if you're familiar with path mapping, it works exactly like
that.
In the above example, if we're importing from "package-name" , TypeScript will try to resolve from
[...]/node_modules/package-name/ts3.1/index.d.ts (and other relevant paths) when running in
TypeScript 3.1. If we import from package-name/foo , we'll try to look for [...]/node_modules/package-
name/ts3.1/foo.d.ts and [...]/node_modules/package-name/ts3.1/foo/index.d.ts .
What if we're not running in TypeScript 3.1 in this example? Well, if none of the fields in typesVersions
get matched, TypeScript falls back to the types field, so here TypeScript 3.0 and earlier will be redirected
to [...]/node_modules/package-name/index.d.ts .
File redirects
When you want to only change the resolution for a single file at a time, you can tell TypeScript the file to re-
solve differently by passing in the exact filenames:
{
"name": "package-name",
"version": "1.0.0",
"types": "./index.d.ts",
"typesVersions": {
"<4.0": { "index.d.ts": ["index.v3.d.ts"] }
}
}
On TypeScript 4.0 and above, an import for "package-name" would resolve to ./index.d.ts and for 3.9
and below "./index.v3.d.ts .
Matching behavior
The way that TypeScript decides on whether a version of the compiler & language matches is by using
Node's semver ranges.
Multiple fields
typesVersions can support multiple fields where each field name is specified by the range to match on.
{
"name": "package-name",
"version": "1.0",
"types": "./index.d.ts",
"typesVersions": {
32
Publishing
Since ranges have the potential to overlap, determining which redirect applies is order-specific. That means
in the above example, even though both the >=3.2 and the >=3.1 matchers support TypeScript 3.2 and
above, reversing the order could have different behavior, so the above sample would not be equivalent to
the following.
{
"name": "package-name",
"version": "1.0",
"types": "./index.d.ts",
"typesVersions": {
// NOTE: this doesn't work!
">=3.1": { "*": ["ts3.1/*"] },
">=3.2": { "*": ["ts3.2/*"] }
}
}
Publish to @types
Packages under the @types organization are published automatically from DefinitelyTyped using the types-
publisher tool. To get your declarations published as an @types package, please submit a pull request to
DefinitelyTyped. You can find more details in the contribution guidelines page.
Go to TOC
33
typescript
global-modifying-module.d.ts
global-plugin.d.ts
global.d.ts
module-class.d.ts
module-function.d.ts
module-plugin.d.ts
module.d.ts
Go to TOC
34
Global: Modifying Module
Global-modifying Modules
A global-modifying module alters existing values in the global scope when they are imported. For example,
there might exist a library which adds new members to String.prototype when imported. This pattern is
somewhat dangerous due to the possibility of runtime conflicts, but we can still write a declaration file for it.
Here is an example
/*~ This is the global-modifying module template file. You should rename it to
index.d.ts
*~ and place it in a folder with the same name as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/
35
typescript
/*~ For example, declaring a method on the module (in addition to its global side
effects) */
export function doSomething(): void;
/*~ If your module exports nothing, you'll need this line. Otherwise, delete it */
export {};
Go to TOC
36
Global: Plugin
UMD
A UMD module is one that can either be used as module (through an import), or as a global (when run in an
environment without a module loader). Many popular libraries, such as Moment.js, are written this way. For
example, in Node.js or using RequireJS, you would write:
console.log(moment.format());
If you see tests for typeof define , typeof window , or typeof module in the code of a library, especial-
ly at the top of the file, it's almost always a UMD library.
Documentation for UMD libraries will also often demonstrate a "Using in Node.js" example showing re‐
quire , and a "Using in the browser" example showing using a <script> tag to load the script.
Template
There are three templates available for modules, module.d.ts , module-class.d.ts and module-
function.d.ts .
var x = require("foo");
// Note: calling 'x' as a function
var y = x(42);
Be sure to read the footnote "The Impact of ES6 on Module Call Signatures"
37
typescript
var x = require("bar");
// Note: using 'new' operator on the imported variable
var y = new x("hello");
For the purposes of writing a declaration file, you'll write the same code whether the module being changed
is a plain module or UMD module.
Template
Use the module-plugin.d.ts template.
Global Plugin
A global plugin is global code that changes the shape of some global. As with global-modifying modules,
these raise the possibility of runtime conflict.
Template
Use the global-plugin.d.ts template.
38
Global: Plugin
Global-modifying Modules
A global-modifying module alters existing values in the global scope when they are imported. For example,
there might exist a library which adds new members to String.prototype when imported. This pattern is
somewhat dangerous due to the possibility of runtime conflicts, but we can still write a declaration file for it.
Template
Use the global-modifying-module.d.ts template.
Consuming Dependencies
There are several kinds of dependencies your library might have. This section shows how to import them
into the declaration file.
Dependencies on Modules
If your library depends on a module, use an import statement:
39
typescript
Footnotes
Preventing Name Conflicts
Note that it's possible to define many types in the global scope when writing a global declaration file. We
strongly discourage this as it leads to possible unresolvable name conflicts when many declaration files are
in a project.
A simple rule to follow is to only declare types namespaced by whatever global variable the library defines.
For example, if the library defines the global value 'cats', you should write
But not
// at top-level
interface CatsKittySettings {}
This guidance also ensures that the library can be transitioned to UMD without breaking declaration file
users.
40
Global: Plugin
In ES6 module loaders, the top-level object (here imported as exp ) can only have properties; the top-level
module object is never callable. The most common solution here is to define a default export for a
callable/constructable object; some module loader shims will automatically detect this situation and replace
the top-level object with the default export.
myLib
+---- index.js
+---- foo.js
+---- bar
+---- index.js
+---- baz.js
var a = require("myLib");
var b = require("myLib/foo");
var c = require("myLib/bar");
var d = require("myLib/bar/baz");
@types/myLib
+---- index.d.ts
+---- foo.d.ts
+---- bar
+---- index.d.ts
+---- baz.d.ts
/*~ Write a declaration for the original type and add new members.
*~ For example, this adds a 'toBinaryString' method with overloads to
*~ the built-in number type.
*/
interface Number {
toBinaryString(opts?: MyLibrary.BinaryFormatOptions): string;
41
typescript
toBinaryString(
callback: MyLibrary.BinaryFormatCallback,
opts?: MyLibrary.BinaryFormatOptions
): string;
}
/*~ If you need to declare several types, place them inside a namespace
*~ to avoid adding too many things to the global namespace.
*/
declare namespace MyLibrary {
type BinaryFormatCallback = (n: number) => string;
interface BinaryFormatOptions {
prefix?: string;
padding: number;
}
}
Go to TOC
42
Global .d.ts
Global Libraries
A global library is one that can be accessed from the global scope (i.e. without using any form of import ).
Many libraries simply expose one or more global variables for use. For example, if you were using jQuery,
the $ variable can be used by simply referring to it:
$(() => {
console.log("hello!");
});
You'll usually see guidance in the documentation of a global library of how to use the library in an HTML
script tag:
<script src="http://a.great.cdn.for/someLib.js"></script>
Today, most popular globally-accessible libraries are actually written as UMD libraries (see below). UMD li-
brary documentation is hard to distinguish from global library documentation. Before writing a global decla-
ration file, make sure the library isn't actually UMD.
function createGreeting(s) {
return "Hello, " + s;
}
or like this:
43
typescript
/*~ If you want the name of this library to be a valid type name,
*~ you can do so here.
*~
*~ For example, this allows us to write 'var x: myLib';
*~ Be sure this actually makes sense! If it doesn't, just
*~ delete this declaration and add types inside the namespace below.
*/
interface myLib {
name: string;
length: number;
extras?: string[];
}
//~ There's some class we can create via 'let c = new myLib.Cat(42)'
//~ Or reference e.g. 'function f(c: myLib.Cat) { ... }
class Cat {
constructor(n: number);
44
Global .d.ts
Go to TOC
45
typescript
For example, when you want to work with JavaScript code which looks like:
/*~ If this module is a UMD module that exposes a global variable 'myClassLib'
when
*~ loaded outside a module loader environment, declare that global here.
*~ Otherwise, delete this declaration.
*/
export as namespace "super-greeter";
greet: void;
/*~ If you want to expose types from your module as well, you can
*~ place them in this block.
*~
*~ Note that if you decide to include this namespace, the module can be
*~ incorrectly imported as a namespace object, unless
46
Module: Class
Go to TOC
47
typescript
For example, when you want to work with JavaScript code which looks like:
greeter(2);
greeter("Hello world");
/*~ If this module is a UMD module that exposes a global variable 'myFuncLib' when
*~ loaded outside a module loader environment, declare that global here.
*~ Otherwise, delete this declaration.
*/
export as namespace myFuncLib;
/*~ This example shows how to have multiple overloads for your function */
declare function Greeter(name: string): Greeter.NamedReturnType;
declare function Greeter(length: number): Greeter.LengthReturnType;
/*~ If you want to expose types from your module as well, you can
*~ place them in this block. Often you will want to describe the
*~ shape of the return type of the function; that type should
*~ be declared in here, as this example shows.
*~
*~ Note that if you decide to include this namespace, the module can be
*~ incorrectly imported as a namespace object, unless
*~ --esModuleInterop is turned on:
*~ import * as x from '[~THE MODULE~]'; // WRONG! DO NOT DO THIS!
*/
declare namespace Greeter {
48
Module: Function
/*~ If the module also has properties, declare them here. For example,
*~ this declaration says that this code is legal:
*~ import f = require('super-greeter');
*~ console.log(f.defaultName);
*/
export const defaultName: string;
export let defaultLength: number;
}
Go to TOC
49
typescript
For example, when you want to work with JavaScript code which extends another library.
/*~ This example shows how to have multiple overloads for your function */
export interface GreeterFunction {
(name: string): void
(time: number): void
}
/*~ This is the module plugin template file. You should rename it to index.d.ts
*~ and place it in a folder with the same name as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/
/*~ On this line, import the module which this module adds to */
import { greeter } from "super-greeter";
/*~ Here, declare the same module as the one you imported above
*~ then we expand the existing declaration of the greeter function
*/
export module "super-greeter" {
export interface GreeterFunction {
/** Greets even better! */
hyperGreet(): void;
}
}
50
Module: Plugin
Go to TOC
51
typescript
function getArrayLength(arr) {
return arr.length;
}
module.exports = {
getArrayLength,
maxInterval,
};
The TypeScript playground can show you the .d.ts equivalent for JavaScript code. You can try it yourself
here.
The .d.ts syntax intentionally looks like ES Modules syntax. ES Modules was ratified by TC39 in 2019,
while it has been available via transpilers for a long time, however if you have a JavaScript codebase using
ES Modules:
Default Exports
In CommonJS you can export any value as the default export, for example here is a regular expression
module:
52
Modules .d.ts
Or a number:
module.exports = 3.142;
One style of exporting in CommonJS is to export a function. Because a function is also an object, then extra
fields can be added and are included in the export.
function getArrayLength(arr) {
return arr.length;
}
getArrayLength.maxInterval = 12;
module.exports = getArrayLength;
Note that using export default in your .d.ts files requires esModuleInterop: true to work. If you can't
have esModuleInterop: true in your project, such as when you're submitting a PR to Definitely Typed,
you'll have to use the export= syntax instead. This older syntax is harder to use but works everywhere.
Here's how the above example would have to be written using export= :
export = getArrayLength;
See Module: Functions for details of how that works, and the Modules reference page.
Covering all of these cases requires the JavaScript code to actually support all of these patterns. To support
many of these patterns, a CommonJS module would need to look something like:
53
typescript
class FastifyInstance {}
function fastify() {
return new FastifyInstance();
}
fastify.FastifyInstance = FastifyInstance;
Types in Modules
You may want to provide a type for JavaScript code which does not exist
function getArrayMetadata(arr) {
return {
length: getArrayLength(arr),
firstObject: arr[0],
};
}
module.exports = {
getArrayMetadata,
};
This example is a good case for using generics to provide richer type information:
Now the type of the array propagates into the ArrayMetadata type.
The types which are exported can then be re-used by consumers of the modules using either import or
import type in TypeScript code or JSDoc imports.
54
Modules .d.ts
For example, you may have complex enough types to describe that you choose to namespace them inside
your .d.ts :
// This namespace is merged with the API class and allows for consumers, and this
file
// to have types which are nested away in their own sections.
declare namespace API {
export interface InfoRequest {
id: string;
}
To understand how namespaces work in .d.ts files read the .d.ts deep dive.
Reference Example
To give you an idea of how all these pieces can come together, here is a reference .d.ts to start with
when making a new module
/*~ This is the module template file. You should rename it to index.d.ts
*~ and place it in a folder with the same name as the module.
*~ For example, if you were writing a file for "super-greeter", this
*~ file should be 'super-greeter/index.d.ts'
*/
/*~ If this module is a UMD module that exposes a global variable 'myLib' when
*~ loaded outside a module loader environment, declare that global here.
*~ Otherwise, delete this declaration.
*/
55
typescript
/*~ You can declare types that are available via importing the module */
export interface SomeType {
name: string;
length: number;
extras?: string[];
}
/*~ You can declare properties of the module using const, let, or var */
export const myField: number;
myLib
+---- index.js
+---- foo.js
+---- bar
+---- index.js
+---- baz.js
var a = require("myLib");
var b = require("myLib/foo");
var c = require("myLib/bar");
var d = require("myLib/bar/baz");
@types/myLib
+---- index.d.ts
+---- foo.d.ts
+---- bar
+---- index.d.ts
+---- baz.d.ts
56
Modules .d.ts
4. When you're happy, clone DefinitelyTyped/DefinitelyTyped and follow the instructions in the
README.
Otherwise
Go to TOC
57
typescript
TypeScript began its life as an attempt to bring traditional object-oriented types to JavaScript so that the
programmers at Microsoft could bring traditional object-oriented programs to the web. As it has developed,
TypeScript's type system has evolved to model code written by native JavaScripters. The resulting system is
powerful, interesting and messy.
This introduction is designed for working Haskell or ML programmers who want to learn TypeScript. It de-
scribes how the type system of TypeScript differs from Haskell's type system. It also describes unique fea-
tures of TypeScript's type system that arise from its modelling of JavaScript code.
This introduction does not cover object-oriented programming. In practice, object-oriented programs in
TypeScript are similar to those in other popular languages with OO features.
Prerequisites
In this introduction, I assume you know the following:
If you need to learn the good parts of JavaScript, read JavaScript: The Good Parts. You may be able to skip
the book if you know how to write programs in a call-by-value lexically scoped language with lots of muta-
bility and not much else. R4RS Scheme is a good example.
The C++ Programming Language is a good place to learn about C-style type syntax. Unlike C++, TypeScript
uses postfix types, like so: x: string instead of string x .
Type Explanation
58
TypeScript for Functional Programmers
number
string
bigint
boolean
symbol
null
undefined
object
Type Explanation
Notes:
1. Function syntax includes parameter names. This is pretty hard to get used to!
// or more precisely:
2. Object literal type syntax closely mirrors object literal value syntax:
3. [T, T] is a subtype of T[] . This is different than Haskell, where tuples are not related to lists.
Boxed types
JavaScript has boxed equivalents of primitive types that contain the methods that programmers associate
with those types. TypeScript reflects this with, for example, the difference between the primitive type num‐
ber and the boxed type Number . The boxed types are rarely needed, since their methods return primitives.
59
typescript
(1).toExponential();
// equivalent to
Number.prototype.toExponential.call(1);
Note that calling a method on a numeric literal requires it to be in parentheses to aid the parser.
Gradual typing
TypeScript uses the type any whenever it can't tell what the type of an expression should be. Compared to
Dynamic , calling any a type is an overstatement. It just turns off the type checker wherever it appears.
For example, you can push any value into an any[] without marking the value in any way:
any is contagious, too — if you initialize a variable with an expression of type any , the variable has type
any too.
To get an error when TypeScript produces an any , use "noImplicitAny": true , or "strict": true in
tsconfig.json .
Structural typing
Structural typing is a familiar concept to most functional programmers, although Haskell and most MLs are
not structurally typed. Its basic form is pretty simple:
// @strict: false
let o = { x: "hi", extra: 1 }; // ok
let o2: { x: string } = o; // ok
Here, the object literal { x: "hi", extra: 1 } has a matching literal type { x: string, extra: num‐
ber } . That type is assignable to { x: string } since it has all the required properties and those proper-
ties have assignable types. The extra property doesn't prevent assignment, it just makes it a subtype of {
x: string } .
Named types just give a name to a type; for assignability purposes there's no difference between the type
alias One and the interface type Two below. They both have a property p: string . (Type aliases behave
differently from interfaces with respect to recursive definitions and type parameters, however.)
60
TypeScript for Functional Programmers
// @errors: 2322
type One = { p: string };
interface Two {
p: string;
}
class Three {
p = "Hello";
}
Unions
In TypeScript, union types are untagged. In other words, they are not discriminated unions like data in
Haskell. However, you can often discriminate types in a union using built-in tags or other properties.
function start(
arg: string | string[] | (() => string) | { s: string }
): string {
// this is super common in JavaScript
if (typeof arg === "string") {
return commonCase(arg);
} else if (Array.isArray(arg)) {
return arg.map(commonCase).join(",");
} else if (typeof arg === "function") {
return commonCase(arg());
} else {
return commonCase(arg.s);
}
string , Array and Function have built-in type predicates, conveniently leaving the object type for the
else branch. It is possible, however, to generate unions that are difficult to differentiate at runtime. For
new code, it's best to build only discriminated unions.
Type Predicate
61
typescript
Type Predicate
array Array.isArray(a)
Note that functions and arrays are objects at runtime, but have their own predicates.
Intersections
In addition to unions, TypeScript also has intersections:
Combined has two properties, a and b , just as if they had been written as one object literal type.
Intersection and union are recursive in case of conflicts, so Conflicting.a: number & string .
Unit types
Unit types are subtypes of primitive types that contain exactly one primitive value. For example, the string
"foo" has the type "foo" . Since JavaScript has no built-in enums, it is common to use a set of well-
known strings instead. Unions of string literal types allow TypeScript to type this pattern:
When needed, the compiler widens — converts to a supertype — the unit type to the primitive type, such as
"foo" to string . This happens when using mutability, which can hamper some uses of mutable
variables:
// @errors: 2345
declare function pad(s: string, n: number, direction: "left" | "right"): string;
// ---cut---
let s = "right";
pad("hi", 10, s); // error: 'string' is not assignable to '"left" | "right"'
"right": "right"
s: string because "right" widens to string on assignment to a mutable variable.
string is not assignable to "left" | "right"
You can work around this with a type annotation for s , but that in turn prevents assignments to s of vari-
ables that are not of type "left" | "right" .
62
TypeScript for Functional Programmers
But it also infers types in a few other places that you may not expect if you've worked with other C-syntax
languages:
Here, n: number in this example also, despite the fact that T and U have not been inferred before the
call. In fact, after [1,2,3] has been used to infer T=number , the return type of n => n.toString() is
used to infer U=string , causing sns to have the type string[] .
Note that inference will work in any order, but intellisense will only work left-to-right, so TypeScript prefers
to declare map with the array first:
Contextual typing also works recursively through object literals, and on unit types that would otherwise be
inferred as string or number . And it can infer return types from context:
1. Declaration initializers are contextually typed by the declaration's type: { inference: string } .
2. The return type of a call uses the contextual type for inferences, so the compiler infers that T={ infer‐
ence: string } .
3. Arrow functions use the contextual type to type their parameters, so the compiler gives o: {
inference: string } .
And it does so while you are typing, so that after typing o. , you get completions for the property infer‐
ence , along with any other properties you'd have in a real program. Altogether, this feature can make
TypeScript's inference look a bit like a unifying type inference engine, but it is not.
Type aliases
Type aliases are mere aliases, just like type in Haskell. The compiler will attempt to use the alias name
wherever it was used in the source code, but does not always succeed.
63
typescript
An FString is just like a normal string, except that the compiler thinks it has a property named __com‐
pileTimeOnly that doesn't actually exist. This means that FString can still be assigned to string , but
not the other way round.
Discriminated Unions
The closest equivalent to data is a union of types with discriminant properties, normally called discriminat-
ed unions in TypeScript:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
Unlike Haskell, the tag, or discriminant, is just a property in each object type. Each variant has an identical
property with a different unit type. This is still a normal union type; the leading | is an optional part of the
union type syntax. You can discriminate the members of the union using normal JavaScript code:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
Note that the return type of area is inferred to be number because TypeScript knows the function is total.
If some variant is not covered, the return type of area will be number | undefined instead.
Also, unlike Haskell, common properties show up in any union, so you can usefully discriminate multiple
members of the union:
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; x: number }
| { kind: "triangle"; x: number; y: number };
// ---cut---
function height(s: Shape) {
if (s.kind === "circle") {
return 2 * s.radius;
64
TypeScript for Functional Programmers
} else {
// s.kind: "square" | "triangle"
return s.x;
}
}
Type Parameters
Like most C-descended languages, TypeScript requires declaration of type parameters:
There is no case requirement, but type parameters are conventionally single uppercase letters. Type para-
meters can also be constrained to a type, which behaves a bit like type class constraints:
TypeScript can usually infer type arguments from a call based on the type of the arguments, so type argu-
ments are usually not needed.
Because TypeScript is structural, it doesn't need type parameters as much as nominal systems. Specifically,
they are not needed to make a function polymorphic. Type parameters should only be used to propagate
type information, such as constraining parameters to be the same type:
In the first length , T is not necessary; notice that it's only referenced once, so it's not being used to con-
strain the type of the return value or other parameters.
Higher-kinded types
TypeScript does not have higher kinded types, so the following is not legal:
Point-free programming
Point-free programming — heavy use of currying and function composition — is possible in JavaScript, but
can be verbose. In TypeScript, type inference often fails for point-free programs, so you'll end up specifying
type parameters instead of value parameters. The result is so verbose that it's usually better to avoid point-
free programming.
65
typescript
Module system
JavaScript's modern module syntax is a bit like Haskell's, except that any file with import or export is
implicitly a module:
You can also import commonjs modules — modules written using node.js' module system:
import f = require("single-function-package");
export { f };
function f() {
return g();
}
function g() {} // g is not exported
The latter style is more common but both are allowed, even in the same file.
interface Rx {
readonly x: number;
}
let rx: Rx = { x: 1 };
rx.x = 12; // error
It also ships with a mapped type Readonly<T> that makes all properties readonly :
interface X {
x: number;
}
let rx: Readonly<X> = { x: 1 };
rx.x = 12; // error
66
TypeScript for Functional Programmers
And it has a specific ReadonlyArray<T> type that removes side-affecting methods and prevents writing to
indices of the array, as well as special syntax for this type:
You can also use a const-assertion, which operates on arrays and object literals:
However, none of these options are the default, so they are not consistently used in TypeScript code.
Next Steps
This doc is a high level overview of the syntax and types you would use in everyday code. From here you
should:
Go to TOC
67
typescript
TypeScript stands in an unusual relationship to JavaScript. TypeScript offers all of JavaScript's features, and
an additional layer on top of these: TypeScript's type system.
For example, JavaScript provides language primitives like string and number , but it doesn't check that
you've consistently assigned these. TypeScript does.
This means that your existing working JavaScript code is also TypeScript code. The main benefit of
TypeScript is that it can highlight unexpected behavior in your code, lowering the chance of bugs.
This tutorial provides a brief overview of TypeScript, focusing on its type system.
Types by Inference
TypeScript knows the JavaScript language and will generate types for you in many cases. For example in
creating a variable and assigning it to a particular value, TypeScript will use the value as its type.
By understanding how JavaScript works, TypeScript can build a type-system that accepts JavaScript code
but has types. This offers a type-system without needing to add extra characters to make types explicit in
your code. That's how TypeScript knows that helloWorld is a string in the above example.
You may have written JavaScript in Visual Studio Code, and had editor auto-completion. Visual Studio Code
uses TypeScript under the hood to make it easier to work with JavaScript.
Defining Types
You can use a wide variety of design patterns in JavaScript. However, some design patterns make it difficult
for types to be inferred automatically (for example, patterns that use dynamic programming). To cover
these cases, TypeScript supports an extension of the JavaScript language, which offers places for you to tell
TypeScript what the types should be.
For example, to create an object with an inferred type which includes name: string and id: number ,
you can write:
const user = {
name: "Hayes",
id: 0,
};
You can explicitly describe this object's shape using an interface declaration:
interface User {
name: string;
id: number;
}
68
TypeScript for JavaScript Programmers
You can then declare that a JavaScript object conforms to the shape of your new interface by using syn-
tax like : TypeName after a variable declaration:
interface User {
name: string;
id: number;
}
// ---cut---
const user: User = {
name: "Hayes",
id: 0,
};
If you provide an object that doesn't match the interface you have provided, TypeScript will warn you:
// @errors: 2322
interface User {
name: string;
id: number;
}
Since JavaScript supports classes and object-oriented programming, so does TypeScript. You can use an in-
terface declaration with classes:
interface User {
name: string;
id: number;
}
class UserAccount {
name: string;
id: number;
You can use interfaces to annotate parameters and return values to functions:
// @noErrors
interface User {
name: string;
id: number;
}
// ---cut---
function getAdminUser(): User {
//...
}
69
typescript
There is already a small set of primitive types available in JavaScript: boolean , bigint , null , number ,
string , symbol , and undefined , which you can use in an interface. TypeScript extends this list with a
few more, such as any (allow anything), unknown (ensure someone using this type declares what the type
is), never (it's not possible that this type could happen), and void (a function which returns undefined
or has no return value).
You'll see that there are two syntaxes for building types: Interfaces and Types. You should prefer inter‐
face . Use type when you need specific features.
Composing Types
With TypeScript, you can create complex types by combining simple ones. There are two popular ways to do
so: with unions, and with generics.
Unions
With a union, you can declare that a type could be one of many types. For example, you can describe a
boolean type as being either true or false :
Note: If you hover over MyBool above, you'll see that it is classed as boolean . That's a property of the
Structural Type System. More on this below.
A popular use-case for union types is to describe the set of string or number literals that a value is al-
lowed to be:
Unions provide a way to handle different types too. For example, you may have a function that takes an
array or a string :
Type Predicate
70
TypeScript for JavaScript Programmers
Type Predicate
array Array.isArray(a)
For example, you can make a function return different values depending on whether it is passed a string or
an array:
Generics
Generics provide variables to types. A common example is an array. An array without generics could contain
anything. An array with generics can describe the values that the array contains.
// @errors: 2345
interface Backpack<Type> {
add: (obj: Type) => void;
get: () => Type;
}
// Since the backpack variable is a string, you can't pass a number to the add
function.
backpack.add(23);
In a structural type system, if two objects have the same shape, they are considered to be of the same
type.
71
typescript
interface Point {
x: number;
y: number;
}
The point variable is never declared to be a Point type. However, TypeScript compares the shape of
point to the shape of Point in the type-check. They have the same shape, so the code passes.
// @errors: 2345
interface Point {
x: number;
y: number;
}
// @errors: 2345
interface Point {
x: number;
y: number;
}
72
TypeScript for JavaScript Programmers
If the object or class has all the required properties, TypeScript will say they match, regardless of the imple-
mentation details.
Next Steps
This was a brief overview of the syntax and tools used in everyday TypeScript. From here, you can:
Go to TOC
73
typescript
TypeScript is a popular choice for programmers accustomed to other languages with static typing, such as
C# and Java.
TypeScript's type system offers many of the same benefits, such as better code completion, earlier detec-
tion of errors, and clearer communication between parts of your program. While TypeScript provides many
familiar features for these developers, it's worth stepping back to see how JavaScript (and therefore
TypeScript) differ from traditional OOP languages. Understanding these differences will help you write better
JavaScript code, and avoid common pitfalls that programmers who go straight from C#/Java to TypeScript
may fall in to.
Co-learning JavaScript
If you're familiar with JavaScript already but are primarily a Java or C# programmer, this introductory page
can help explain some of the common misconceptions and pitfalls you might be susceptible to. Some of the
ways that TypeScript models types are quite different from Java or C#, and it's important to keep these in
mind when learning TypeScript.
If you're a Java or C# programmer that is new to JavaScript in general, we recommend learning a little bit
of JavaScript without types first to understand JavaScript's runtime behaviors. Because TypeScript doesn't
change how your code runs, you'll still have to learn how JavaScript works in order to write code that actu-
ally does something!
It's important to remember that TypeScript uses the same runtime as JavaScript, so any resources about
how to accomplish specific runtime behavior (converting a string to a number, displaying an alert, writing a
file to disk, etc.) will always apply equally well to TypeScript programs. Don't limit yourself to TypeScript-
specific resources!
Static Classes
Additionally, certain constructs from C# and Java such as singletons and static classes are unnecessary in
TypeScript.
74
TypeScript for Java/C# Programmers
OOP in TypeScript
That said, you can still use classes if you like! Some problems are well-suited to being solved by a tradition-
al OOP hierarchy, and TypeScript's support for JavaScript classes will make these models even more power-
ful. TypeScript supports many common patterns such as implementing interfaces, inheritance, and static
methods.
Rethinking Types
TypeScript's understanding of a type is actually quite different from C# or Java's. Let's explore some
differences.
These aspects describe a reified, nominal type system. The types we wrote in the code are present at run-
time, and the types are related via their declarations, not their structures.
Types as Sets
In C# or Java, it's meaningful to think of a one-to-one correspondence between runtime types and their
compile-time declarations.
In TypeScript, it's better to think of a type as a set of values that share something in common. Because
types are just sets, a particular value can belong to many sets at the same time.
Once you start thinking of types as sets, certain operations become very natural. For example, in C#, it's
awkward to pass around a value that is either a string or int , because there isn't a single type that rep-
resents this sort of value.
In TypeScript, this becomes very natural once you realize that every type is just a set. How do you describe
a value that either belongs in the string set or the number set? It simply belongs to the union of those
sets: string | number .
TypeScript provides a number of mechanisms to work with types in a set-theoretic way, and you'll find them
more intuitive if you think of types as sets.
75
typescript
interface Pointlike {
x: number;
y: number;
}
interface Named {
name: string;
}
const obj = {
x: 0,
y: 0,
name: "Origin",
};
logPoint(obj);
logName(obj);
TypeScript's type system is structural, not nominal: We can use obj as a Pointlike because it has x
and y properties that are both numbers. The relationships between types are determined by the properties
they contain, not whether they were declared with some particular relationship.
TypeScript's type system is also not reified: There's nothing at runtime that will tell us that obj is
Pointlike . In fact, the Pointlike type is not present in any form at runtime.
Going back to the idea of types as sets, we can think of obj as being a member of both the Pointlike
set of values and the Named set of values.
Empty Types
class Empty {}
76
TypeScript for Java/C# Programmers
TypeScript determines if the call to fn here is valid by seeing if the provided argument is a valid Empty . It
does so by examining the structure of { k: 10 } and class Empty { } . We can see that { k: 10 }
has all of the properties that Empty does, because Empty has no properties. Therefore, this is a valid call!
This may seem surprising, but it's ultimately a very similar relationship to one enforced in nominal OOP lan-
guages. A subclass cannot remove a property of its base class, because doing so would destroy the natural
subtype relationship between the derived class and its base. Structural type systems simply identify this re-
lationship implicitly by describing subtypes in terms of having properties of compatible types.
Identical Types
class Car {
drive() {
// hit the gas
}
}
class Golfer {
drive() {
// hit the ball far
}
}
// No error?
let w: Car = new Golfer();
Again, this isn't an error because the structures of these classes are the same. While this may seem like a
potential source of confusion, in practice, identical classes that shouldn't be related are not common.
We'll learn more about how classes relate to each other in the Classes chapter.
Reflection
OOP programmers are accustomed to being able to query the type of any value, even a generic one:
// C#
static void LogType<T>() {
Console.WriteLine(typeof(T).Name);
}
Because TypeScript's type system is fully erased, information about e.g. the instantiation of a generic type
parameter is not available at runtime.
JavaScript does have some limited primitives like typeof and instanceof , but remember that these op-
erators are still working on the values as they exist in the type-erased output code. For example, typeof
(new Car()) will be "object" , not Car or "Car" .
77
typescript
Next Steps
This was a brief overview of the syntax and tools used in everyday TypeScript. From here, you can:
Go to TOC
78
TypeScript for the New Programmer
Congratulations on choosing TypeScript as one of your first languages — you're already making good
decisions!
You've probably already heard that TypeScript is a "flavor" or "variant" of JavaScript. The relationship be-
tween TypeScript (TS) and JavaScript (JS) is rather unique among modern programming languages, so
learning more about this relationship will help you understand how TypeScript adds to JavaScript.
Web browser developers responded to this increased JS usage by optimizing their execution engines (dy-
namic compilation) and extending what could be done with it (adding APIs), which in turn made web devel-
opers use it even more. On modern websites, your browser is frequently running applications that span
hundreds of thousands of lines of code. This is long and gradual growth of "the web", starting as a simple
network of static pages, and evolving into a platform for rich applications of all kinds.
More than this, JS has become popular enough to be used outside the context of browsers, such as imple-
menting JS servers using node.js. The "run anywhere" nature of JS makes it an attractive choice for cross-
platform development. There are many developers these days that use only JavaScript to program their en-
tire stack!
To summarize, we have a language that was designed for quick uses, and then grew to a full-fledged tool to
write applications with millions of lines. Every language has its own quirks — oddities and surprises, and
JavaScript's humble beginning makes it have many of these. Some examples:
if ("" == 0) {
// It is! But why??
}
if (1 < x < 3) {
// True for *any* value of x!
}
79
typescript
Most programming languages would throw an error when these sorts of errors occur, some would do so dur-
ing compilation — before any code is running. When writing small programs, such quirks are annoying but
manageable; when writing applications with hundreds or thousands of lines of code, these constant surpris-
es are a serious problem.
TypeScript checks a program for errors before execution, and does so based on the kinds of values, it's a
static type checker. For example, the last example above has an error because of the type of obj . Here's
the error TypeScript found:
// @errors: 2551
const obj = { width: 10, height: 15 };
const area = obj.width * obj.heigth;
Syntax
TypeScript is a language that is a superset of JavaScript: JS syntax is therefore legal TS. Syntax refers to
the way we write text to form a program. For example, this code has a syntax error because it's missing a
):
// @errors: 1005
let a = (4
TypeScript doesn't consider any JavaScript code to be an error because of its syntax. This means you can
take any working JavaScript code and put it in a TypeScript file without worrying about exactly how it is
written.
Types
However, TypeScript is a typed superset, meaning that it adds rules about how different kinds of values can
be used. The earlier error about obj.heigth was not a syntax error: it is an error of using some kind of
value (a type) in an incorrect way.
As another example, this is JavaScript code that you can run in your browser, and it will log a value:
console.log(4 / []);
This syntactically-legal program logs Infinity . TypeScript, though, considers division of number by an ar-
ray to be a nonsensical operation, and will issue an error:
80
TypeScript for the New Programmer
// @errors: 2363
console.log(4 / []);
It's possible you really did intend to divide a number by an array, perhaps just to see what happens, but
most of the time, though, this is a programming mistake. TypeScript's type checker is designed to allow
correct programs through while still catching as many common errors as possible. (Later, we'll learn about
settings you can use to configure how strictly TypeScript checks your code.)
If you move some code from a JavaScript file to a TypeScript file, you might see type errors depending on
how the code is written. These may be legitimate problems with the code, or TypeScript being overly con-
servative. Throughout this guide we'll demonstrate how to add various TypeScript syntax to eliminate such
errors.
Runtime Behavior
TypeScript is also a programming language that preserves the runtime behavior of JavaScript. For example,
dividing by zero in JavaScript produces Infinity instead of throwing a runtime exception. As a principle,
TypeScript never changes the runtime behavior of JavaScript code.
This means that if you move code from JavaScript to TypeScript, it is guaranteed to run the same way,
even if TypeScript thinks that the code has type errors.
Keeping the same runtime behavior as JavaScript is a foundational promise of TypeScript because it means
you can easily transition between the two languages without worrying about subtle differences that might
make your program stop working.
Erased Types
Roughly speaking, once TypeScript's compiler is done with checking your code, it erases the types to pro-
duce the resulting "compiled" code. This means that once your code is compiled, the resulting plain JS code
has no type information.
This also means that TypeScript never changes the behavior of your program based on the types it inferred.
The bottom line is that while you might see type errors during compilation, the type system itself has no
bearing on how your program works when it runs.
Finally, TypeScript doesn't provide any additional runtime libraries. Your programs will use the same stan-
dard library (or external libraries) as JavaScript programs, so there's no additional TypeScript-specific
framework to learn.
The answer is that you can't learn TypeScript without learning JavaScript! TypeScript shares syntax and run-
time behavior with JavaScript, so anything you learn about JavaScript is helping you learn TypeScript at the
same time.
81
typescript
There are many, many resources available for programmers to learn JavaScript; you should not ignore
these resources if you're writing TypeScript. For example, there are about 20 times more StackOverflow
questions tagged javascript than typescript , but all of the javascript questions also apply to
TypeScript.
If you find yourself searching for something like "how to sort a list in TypeScript", remember: TypeScript is
JavaScript's runtime with a compile-time type checker. The way you sort a list in TypeScript is the
same way you do so in JavaScript. If you find a resource that uses TypeScript directly, that's great too, but
don't limit yourself to thinking you need TypeScript-specific answers for everyday questions about how to
accomplish runtime tasks.
Next Steps
This was a brief overview of the syntax and tools used in everyday TypeScript. From here, you can:
Go to TOC
82
Basic Types
For programs to be useful, we need to be able to work with some of the simplest units of data: numbers,
strings, structures, boolean values, and the like. In TypeScript, we support the same types as you would ex-
pect in JavaScript, with an extra enumeration type thrown in to help things along.
Boolean
The most basic datatype is the simple true/false value, which JavaScript and TypeScript call a boolean
value.
Number
As in JavaScript, all numbers in TypeScript are either floating point values or BigIntegers. These floating
point numbers get the type number , while BigIntegers get the type bigint . In addition to hexadecimal
and decimal literals, TypeScript also supports binary and octal literals introduced in ECMAScript 2015.
// @target: ES2020
let decimal: number = 6;
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let big: bigint = 100n;
String
Another fundamental part of creating programs in JavaScript for webpages and servers alike is working with
textual data. As in other languages, we use the type string to refer to these textual datatypes. Just like
JavaScript, TypeScript also uses double quotes ( " ) or single quotes ( ' ) to surround string data.
You can also use template strings, which can span multiple lines and have embedded expressions. These
strings are surrounded by the backtick/backquote ( ` ) character, and embedded expressions are of the
form ${ expr } .
83
typescript
Array
TypeScript, like JavaScript, allows you to work with arrays of values. Array types can be written in one of
two ways. In the first, you use the type of the elements followed by [] to denote an array of that element
type:
Tuple
Tuple types allow you to express an array with a fixed number of elements whose types are known, but
need not be the same. For example, you may want to represent a value as a pair of a string and a num‐
ber :
// @errors: 2322
// Declare a tuple type
let x: [string, number];
// Initialize it
x = ["hello", 10]; // OK
// Initialize it incorrectly
x = [10, "hello"]; // Error
When accessing an element with a known index, the correct type is retrieved:
// @errors: 2339
let x: [string, number];
x = ["hello", 10]; // OK
/// ---cut---
// OK
console.log(x[0].substring(1));
console.log(x[1].substring(1));
Accessing an element outside the set of known indices fails with an error:
console.log(x[5].toString());
84
Basic Types
Enum
A helpful addition to the standard set of datatypes from JavaScript is the enum . As in languages like C#, an
enum is a way of giving more friendly names to sets of numeric values.
enum Color {
Red,
Green,
Blue,
}
let c: Color = Color.Green;
By default, enums begin numbering their members starting at 0 . You can change this by manually setting
the value of one of its members. For example, we can start the previous example at 1 instead of 0 :
enum Color {
Red = 1,
Green,
Blue,
}
let c: Color = Color.Green;
enum Color {
Red = 1,
Green = 2,
Blue = 4,
}
let c: Color = Color.Green;
A handy feature of enums is that you can also go from a numeric value to the name of that value in the
enum. For example, if we had the value 2 but weren't sure what that mapped to in the Color enum
above, we could look up the corresponding name:
enum Color {
Red = 1,
Green,
Blue,
}
let colorName: string = Color[2];
// Displays 'Green'
console.log(colorName);
Unknown
We may need to describe the type of variables that we do not know when we are writing an application.
These values may come from dynamic content – e.g. from the user – or we may want to intentionally ac-
cept all values in our API. In these cases, we want to provide a type that tells the compiler and future read-
ers that this variable could be anything, so we give it the unknown type.
85
typescript
If you have a variable with an unknown type, you can narrow it to something more specific by doing type‐
of checks, comparison checks, or more advanced type guards that will be discussed in a later chapter:
Any
In some situations, not all type information is available or its declaration would take an inappropriate
amount of effort. These may occur for values from code that has been written without TypeScript or a 3rd
party library. In these cases, we might want to opt-out of type checking. To do so, we label these values
with the any type:
The any type is a powerful way to work with existing JavaScript, allowing you to gradually opt-in and opt-
out of type checking during compilation.
Unlike unknown , variables of type any allow you to access arbitrary properties, even ones that don't exist.
These properties include functions and TypeScript will not check their existence or type:
// @errors: 2571
let looselyTyped: any = 4;
// OK, ifItExists might exist at runtime
looselyTyped.ifItExists();
// OK, toFixed exists (but the compiler doesn't check)
looselyTyped.toFixed();
86
Basic Types
After all, remember that all the convenience of any comes at the cost of losing type safety. Type safety is
one of the main motivations for using TypeScript and you should try to avoid using any when not
necessary.
Void
void is a little like the opposite of any : the absence of having any type at all. You may commonly see this
as the return type of functions that do not return a value:
Declaring variables of type void is not useful because you can only assign null (only if strictNull‐
Checks is not specified, see next section) or undefined to them:
// @strict: false
let unusable: void = undefined;
// OK if `--strictNullChecks` is not given
unusable = null;
By default null and undefined are subtypes of all other types. That means you can assign null and
undefined to something like number .
However, when using the strictNullChecks flag, null and undefined are only assignable to unknown ,
any and their respective types (the one exception being that undefined is also assignable to void ). This
helps avoid many common errors. In cases where you want to pass in either a string or null or unde‐
fined , you can use the union type string | null | undefined .
Union types are an advanced topic that we'll cover in a later chapter.
87
typescript
As a note: we encourage the use of strictNullChecks when possible, but for the purposes of this
handbook, we will assume it is turned off.
Never
The never type represents the type of values that never occur. For instance, never is the return type for
a function expression or an arrow function expression that always throws an exception or one that never re-
turns. Variables also acquire the type never when narrowed by any type guards that can never be true.
The never type is a subtype of, and assignable to, every type; however, no type is a subtype of, or as-
signable to, never (except never itself). Even any isn't assignable to never .
Object
object is a type that represents the non-primitive type, i.e. anything that is not number , string , bool‐
ean , bigint , symbol , null , or undefined .
With object type, APIs like Object.create can be better represented. For example:
// @errors: 2345
declare function create(o: object | null): void;
// OK
create({ prop: 0 });
create(null);
create(undefined); // with `--strictNullChecks` flag enabled, undefined is not a
subtype of null
create(42);
create("string");
create(false);
88
Basic Types
Type assertions
Sometimes you'll end up in a situation where you'll know more about a value than TypeScript does. Usually,
this will happen when you know the type of some entity could be more specific than its current type.
Type assertions are a way to tell the compiler "trust me, I know what I'm doing." A type assertion is like a
type cast in other languages, but it performs no special checking or restructuring of data. It has no runtime
impact and is used purely by the compiler. TypeScript assumes that you, the programmer, have performed
any special checks that you need.
The two samples are equivalent. Using one over the other is mostly a choice of preference; however, when
using TypeScript with JSX, only as -style assertions are allowed.
// @errors: 2339
function reverse(s: String): String {
return s.split("").reverse().join("");
}
reverse("hello world");
Instead, use the types number , string , boolean , object and symbol .
89
typescript
reverse("hello world");
Go to TOC
90
Classes
Traditional JavaScript uses functions and prototype-based inheritance to build up reusable components, but
this may feel a bit awkward to programmers more comfortable with an object-oriented approach, where
classes inherit functionality and objects are built from these classes. Starting with ECMAScript 2015, also
known as ECMAScript 6, JavaScript programmers can build their applications using this object-oriented
class-based approach. In TypeScript, we allow developers to use these techniques now, and compile them
down to JavaScript that works across all major browsers and platforms, without having to wait for the next
version of JavaScript.
Classes
Let's take a look at a simple class-based example:
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
The syntax should look familiar if you've used C# or Java before. We declare a new class Greeter . This
class has three members: a property called greeting , a constructor, and a method greet .
You'll notice that in the class when we refer to one of the members of the class we prepend this. . This
denotes that it's a member access.
In the last line we construct an instance of the Greeter class using new . This calls into the constructor we
defined earlier, creating a new object with the Greeter shape, and running the constructor to initialize it.
Inheritance
In TypeScript, we can use common object-oriented patterns. One of the most fundamental patterns in class-
based programming is being able to extend existing classes to create new ones using inheritance.
class Animal {
move(distanceInMeters: number = 0) {
console.log(`Animal moved ${distanceInMeters}m.`);
}
}
91
typescript
This example shows the most basic inheritance feature: classes inherit properties and methods from base
classes. Here, Dog is a derived class that derives from the Animal base class using the extends
keyword. Derived classes are often called subclasses, and base classes are often called superclasses.
Because Dog extends the functionality from Animal , we were able to create an instance of Dog that could
both bark() and move() .
class Animal {
name: string;
constructor(theName: string) {
this.name = theName;
}
move(distanceInMeters: number = 0) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
sam.move();
tom.move(34);
This example covers a few other features we didn't previously mention. Again, we see the extends key-
words used to create two new subclasses of Animal : Horse and Snake .
92
Classes
One difference from the prior example is that each derived class that contains a constructor function must
call super() which will execute the constructor of the base class. What's more, before we ever access a
property on this in a constructor body, we have to call super() . This is an important rule that TypeScript
will enforce.
The example also shows how to override methods in the base class with methods that are specialized for
the subclass. Here both Snake and Horse create a move method that overrides the move from Animal ,
giving it functionality specific to each class. Note that even though tom is declared as an Animal , since its
value is a Horse , calling tom.move(34) will call the overriding method in Horse :
Slithering...
Sammy the Python moved 5m.
Galloping...
Tommy the Palomino moved 34m.
You may still mark a member public explicitly. We could have written the Animal class from the previous
section in the following way:
class Animal {
public name: string;
// @errors: 18013
class Animal {
#name: string;
constructor(theName: string) {
this.#name = theName;
}
}
new Animal("Cat").#name;
93
typescript
This syntax is built into the JavaScript runtime and can have better guarantees about the isolation of each
private field. Right now, the best documentation for these private fields is in the TypeScript 3.8 release
notes.
// @errors: 2341
class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
new Animal("Cat").name;
TypeScript is a structural type system. When we compare two different types, regardless of where they
came from, if the types of all members are compatible, then we say the types themselves are compatible.
However, when comparing types that have private and protected members, we treat these types differ-
ently. For two types to be considered compatible, if one of them has a private member, then the other
must have a private member that originated in the same declaration. The same applies to protected
members.
Let's look at an example to better see how this plays out in practice:
// @errors: 2322
class Animal {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
class Employee {
private name: string;
constructor(theName: string) {
this.name = theName;
}
}
94
Classes
animal = rhino;
animal = employee;
In this example, we have an Animal and a Rhino , with Rhino being a subclass of Animal . We also have
a new class Employee that looks identical to Animal in terms of shape. We create some instances of these
classes and then try to assign them to each other to see what will happen. Because Animal and Rhino
share the private side of their shape from the same declaration of private name: string in Animal ,
they are compatible. However, this is not the case for Employee . When we try to assign from an Employee
to Animal we get an error that these types are not compatible. Even though Employee also has a pri‐
vate member called name , it's not the one we declared in Animal .
Understanding protected
The protected modifier acts much like the private modifier with the exception that members declared
protected can also be accessed within deriving classes. For example,
// @errors: 2445
class Person {
protected name: string;
constructor(name: string) {
this.name = name;
}
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
Notice that while we can't use name from outside of Person , we can still use it from within an instance
method of Employee because Employee derives from Person .
A constructor may also be marked protected . This means that the class cannot be instantiated outside of
its containing class, but can be extended. For example,
// @errors: 2674
class Person {
protected name: string;
protected constructor(theName: string) {
this.name = theName;
}
}
95
typescript
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
}
Readonly modifier
You can make properties readonly by using the readonly keyword. Readonly properties must be initialized
at their declaration or in the constructor.
// @errors: 2540
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor(theName: string) {
this.name = theName;
}
}
Parameter properties
In our last example, we had to declare a readonly member name and a constructor parameter theName in
the Octopus class. This is needed in order to have the value of theName accessible after the Octopus
constructor is executed. Parameter properties let you create and initialize a member in one place. Here's a
further revision of the previous Octopus class using a parameter property:
class Octopus {
readonly numberOfLegs: number = 8;
constructor(readonly name: string) {}
}
Notice how we dropped theName altogether and just use the shortened readonly name: string parame-
ter on the constructor to create and initialize the name member. We've consolidated the declarations and
assignment into one location.
96
Classes
Parameter properties are declared by prefixing a constructor parameter with an accessibility modifier or
readonly , or both. Using private for a parameter property declares and initializes a private member;
likewise, the same is done for public , protected , and readonly .
Accessors
TypeScript supports getters/setters as a way of intercepting accesses to a member of an object. This gives
you a way of having finer-grained control over how a member is accessed on each object.
Let's convert a simple class to use get and set . First, let's start with an example without getters and
setters.
// @strict: false
class Employee {
fullName: string;
}
if (employee.fullName) {
console.log(employee.fullName);
}
While allowing people to randomly set fullName directly is pretty handy, we may also want enforce some
constraints when fullName is set.
In this version, we add a setter that checks the length of the newName to make sure it's compatible with
the max-length of our backing database field. If it isn't we throw an error notifying client code that some-
thing went wrong.
To preserve existing functionality, we also add a simple getter that retrieves fullName unmodified.
// @strict: false
const fullNameMaxLength = 10;
class Employee {
private _fullName: string = "";
this._fullName = newName;
}
}
97
typescript
if (employee.fullName) {
console.log(employee.fullName);
}
To prove to ourselves that our accessor is now checking the length of values, we can attempt to assign a
name longer than 10 characters and verify that we get an error.
First, accessors require you to set the compiler to output ECMAScript 5 or higher. Downleveling to
ECMAScript 3 is not supported. Second, accessors with a get and no set are automatically inferred to be
readonly . This is helpful when generating a .d.ts file from your code, because users of your property
can see that they can't change it.
Static Properties
Up to this point, we've only talked about the instance members of the class, those that show up on the ob-
ject when it's instantiated. We can also create static members of a class, those that are visible on the class
itself rather than on the instances. In this example, we use static on the origin, as it's a general value for
all grids. Each instance accesses this value through prepending the name of the class. Similarly to prepend-
ing this. in front of instance accesses, here we prepend Grid. in front of static accesses.
class Grid {
static origin = { x: 0, y: 0 };
Abstract Classes
Abstract classes are base classes from which other classes may be derived. They may not be instantiated
directly. Unlike an interface, an abstract class may contain implementation details for its members. The ab‐
stract keyword is used to define abstract classes as well as abstract methods within an abstract class.
move(): void {
console.log("roaming the earth...");
}
}
98
Classes
Methods within an abstract class that are marked as abstract do not contain an implementation and must be
implemented in derived classes. Abstract methods share a similar syntax to interface methods. Both define
the signature of a method without including a method body. However, abstract methods must include the
abstract keyword and may optionally include access modifiers.
printName(): void {
console.log("Department name: " + this.name);
}
printMeeting(): void {
console.log("The Accounting Department meets each Monday at 10am.");
}
generateReports(): void {
console.log("Generating accounting reports...");
}
}
Advanced Techniques
Constructor functions
When you declare a class in TypeScript, you are actually creating multiple declarations at the same time.
The first is the type of the instance of the class.
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
99
typescript
}
}
Here, when we say let greeter: Greeter , we're using Greeter as the type of instances of the class
Greeter . This is almost second nature to programmers from other object-oriented languages.
We're also creating another value that we call the constructor function. This is the function that is called
when we new up instances of the class. To see what this looks like in practice, let's take a look at the
JavaScript created by the above example:
// @strict: false
let Greeter = (function () {
function Greeter(message) {
this.greeting = message;
}
Greeter.prototype.greet = function () {
return "Hello, " + this.greeting;
};
return Greeter;
})();
let greeter;
greeter = new Greeter("world");
console.log(greeter.greet()); // "Hello, world"
Here, let Greeter is going to be assigned the constructor function. When we call new and run this func-
tion, we get an instance of the class. The constructor function also contains all of the static members of the
class. Another way to think of each class is that there is an instance side and a static side.
// @strict: false
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
greet() {
if (this.greeting) {
return "Hello, " + this.greeting;
} else {
return Greeter.standardGreeting;
}
}
}
100
Classes
In this example, greeter1 works similarly to before. We instantiate the Greeter class, and use this ob-
ject. This we have seen before.
Next, we then use the class directly. Here we create a new variable called greeterMaker . This variable will
hold the class itself, or said another way its constructor function. Here we use typeof Greeter , that is
"give me the type of the Greeter class itself" rather than the instance type. Or, more precisely, "give me
the type of the symbol called Greeter ," which is the type of the constructor function. This type will contain
all of the static members of Greeter along with the constructor that creates instances of the Greeter class.
We show this by using new on greeterMaker , creating new instances of Greeter and invoking them as
before. It is also good to mention that changing static property is frowned upon, here greeter3 has "Hey
there!" instead of "Hello, there" on standardGreeting .
// @strict: false
class Point {
x: number;
y: number;
}
Go to TOC
101
typescript
Functions are the fundamental building block of any application in JavaScript. They're how you build up lay-
ers of abstraction, mimicking classes, information hiding, and modules. In TypeScript, while there are class-
es, namespaces, and modules, functions still play the key role in describing how to do things. TypeScript
also adds some new capabilities to the standard JavaScript functions to make them easier to work with.
Functions
To begin, just as in JavaScript, TypeScript functions can be created both as a named function or as an
anonymous function. This allows you to choose the most appropriate approach for your application, whether
you're building a list of functions in an API or a one-off function to hand off to another function.
// @strict: false
// Named function
function add(x, y) {
return x + y;
}
// Anonymous function
let myAdd = function (x, y) {
return x + y;
};
Just as in JavaScript, functions can refer to variables outside of the function body. When they do so, they're
said to capture these variables. While understanding how this works (and the trade-offs when using this
technique) is outside of the scope of this article, having a firm understanding how this mechanic works is an
important piece of working with JavaScript and TypeScript.
// @strict: false
let z = 100;
function addToZ(x, y) {
return x + y + z;
}
Function Types
Typing the function
Let's add types to our simple examples from earlier:
102
Functions
We can add types to each of the parameters and then to the function itself to add a return type. TypeScript
can figure the return type out by looking at the return statements, so we can also optionally leave this off in
many cases.
A function's type has the same two parts: the type of the arguments and the return type. When writing out
the whole function type, both parts are required. We write out the parameter types just like a parameter
list, giving each parameter a name and a type. This name is just to help with readability. We could have in-
stead written:
As long as the parameter types line up, it's considered a valid type for the function, regardless of the names
you give the parameters in the function type.
The second part is the return type. We make it clear which is the return type by using an arrow ( => ) be-
tween the parameters and the return type. As mentioned before, this is a required part of the function type,
so if the function doesn't return a value, you would use void instead of leaving it off.
Of note, only the parameters and the return type make up the function type. Captured variables are not re-
flected in the type. In effect, captured variables are part of the "hidden state" of any function and do not
make up its API.
103
typescript
let myAdd2: (baseValue: number, increment: number) => number = function (x, y) {
return x + y;
};
This is called "contextual typing", a form of type inference. This helps cut down on the amount of effort to
keep your program typed.
// @errors: 2554
function buildName(firstName: string, lastName: string) {
return firstName + " " + lastName;
}
In JavaScript, every parameter is optional, and users may leave them off as they see fit. When they do,
their value is undefined . We can get this functionality in TypeScript by adding a ? to the end of parame-
ters we want to be optional. For example, let's say we want the last name parameter from above to be
optional:
// @errors: 2554
function buildName(firstName: string, lastName?: string) {
if (lastName) return firstName + " " + lastName;
else return firstName;
}
Any optional parameters must follow required parameters. Had we wanted to make the first name optional,
rather than the last name, we would need to change the order of parameters in the function, putting the
first name last in the list.
In TypeScript, we can also set a value that a parameter will be assigned if the user does not provide one, or
if the user passes undefined in its place. These are called default-initialized parameters. Let's take the
previous example and default the last name to "Smith" .
// @errors: 2554
function buildName(firstName: string, lastName = "Smith") {
return firstName + " " + lastName;
}
104
Functions
Default-initialized parameters that come after all required parameters are treated as optional, and just like
optional parameters, can be omitted when calling their respective function. This means optional parameters
and trailing default parameters will share commonality in their types, so both
and
share the same type (firstName: string, lastName?: string) => string . The default value of last‐
Name disappears in the type, only leaving behind the fact that the parameter is optional.
Unlike plain optional parameters, default-initialized parameters don't need to occur after required parame-
ters. If a default-initialized parameter comes before a required parameter, users need to explicitly pass un‐
defined to get the default initialized value. For example, we could write our last example with only a de-
fault initializer on firstName :
// @errors: 2554
function buildName(firstName = "Will", lastName: string) {
return firstName + " " + lastName;
}
Rest Parameters
Required, optional, and default parameters all have one thing in common: they talk about one parameter at
a time. Sometimes, you want to work with multiple parameters as a group, or you may not know how many
parameters a function will ultimately take. In JavaScript, you can work with the arguments directly using
the arguments variable that is visible inside every function body.
105
typescript
Rest parameters are treated as a boundless number of optional parameters. When passing arguments for a
rest parameter, you can use as many as you want; you can even pass none. The compiler will build an array
of the arguments passed in with the name given after the ellipsis ( ... ), allowing you to use it in your
function.
The ellipsis is also used in the type of the function with rest parameters:
this
Learning how to use this in JavaScript is something of a rite of passage. Since TypeScript is a superset of
JavaScript, TypeScript developers also need to learn how to use this and how to spot when it's not being
used correctly. Fortunately, TypeScript lets you catch incorrect uses of this with a couple of techniques. If
you need to learn how this works in JavaScript, though, first read Yehuda Katz's Understanding JavaScript
Function Invocation and "this". Yehuda's article explains the inner workings of this very well, so we'll just
cover the basics here.
// @strict: false
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function () {
return function () {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
106
Functions
Notice that createCardPicker is a function that itself returns a function. If we tried to run the example,
we would get an error instead of the expected alert box. This is because the this being used in the func-
tion created by createCardPicker will be set to window instead of our deck object. That's because we
call cardPicker() on its own. A top-level non-method syntax call like this will use window for this .
(Note: under strict mode, this will be undefined rather than window ).
We can fix this by making sure the function is bound to the correct this before we return the function to
be used later. This way, regardless of how it's later used, it will still be able to see the original deck object.
To do this, we change the function expression to use the ECMAScript 6 arrow syntax. Arrow functions cap-
ture the this where the function is created rather than where it is invoked:
// @strict: false
let deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
createCardPicker: function () {
// NOTE: the line below is now an arrow function, allowing us to capture
'this' right here
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
Even better, TypeScript will warn you when you make this mistake if you pass the noImplicitThis flag to
the compiler. It will point out that this in this.suits[pickedSuit] is of type any .
this parameters
Unfortunately, the type of this.suits[pickedSuit] is still any . That's because this comes from the
function expression inside the object literal. To fix this, you can provide an explicit this parameter. this
parameters are fake parameters that come first in the parameter list of a function:
Let's add a couple of interfaces to our example above, Card and Deck , to make the types clearer and eas-
ier to reuse:
interface Card {
suit: string;
card: number;
}
107
typescript
interface Deck {
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
Now TypeScript knows that createCardPicker expects to be called on a Deck object. That means that
this is of type Deck now, not any , so noImplicitThis will not cause any errors.
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
this: void means that addClickListener expects onclick to be a function that does not require a
this type. Second, annotate your calling code with this :
// @strict: false
// @errors: 2345
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
interface Event {
message: string;
}
declare const uiElement: UIElement;
// ---cut---
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// oops, used `this` here. using this callback would crash at runtime
108
Functions
this.info = e.message;
}
}
With this annotated, you make it explicit that onClickBad must be called on an instance of Handler .
Then TypeScript will detect that addClickListener requires a function that has this: void . To fix the
error, change the type of this :
// @strict: false
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
interface Event {
message: string;
}
declare const uiElement: UIElement;
// ---cut---
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// can't use `this` here because it's of type void!
console.log("clicked!");
}
}
Because onClickGood specifies its this type as void , it is legal to pass to addClickListener . Of
course, this also means that it can't use this.info . If you want both then you'll have to use an arrow
function:
// @strict: false
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
interface Event {
message: string;
}
declare const uiElement: UIElement;
// ---cut---
class Handler {
info: string;
onClickGood = (e: Event) => {
this.info = e.message;
};
}
This works because arrow functions use the outer this , so you can always pass them to something that
expects this: void . The downside is that one arrow function is created per object of type Handler.
Methods, on the other hand, are only created once and attached to Handler's prototype. They are shared
between all objects of type Handler.
109
typescript
Overloads
JavaScript is inherently a very dynamic language. It's not uncommon for a single JavaScript function to re-
turn different types of objects based on the shape of the arguments passed in.
// @strict: false
let suits = ["hearts", "spades", "clubs", "diamonds"];
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];
Here, the pickCard function will return two different things based on what the user has passed in. If the
users passes in an object that represents the deck, the function will pick the card. If the user picks the card,
we tell them which card they've picked. But how do we describe this to the type system?
The answer is to supply multiple function types for the same function as a list of overloads. This list is what
the compiler will use to resolve function calls. Let's create a list of overloads that describe what our pick‐
Card accepts and what it returns.
110
Functions
}
}
let myDeck = [
{ suit: "diamonds", card: 2 },
{ suit: "spades", card: 10 },
{ suit: "hearts", card: 4 },
];
With this change, the overloads now give us type checked calls to the pickCard function.
In order for the compiler to pick the correct type check, it follows a similar process to the underlying
JavaScript. It looks at the overload list and, proceeding with the first overload, attempts to call the function
with the provided parameters. If it finds a match, it picks this overload as the correct overload. For this rea-
son, it's customary to order overloads from most specific to least specific.
Note that the function pickCard(x): any piece is not part of the overload list, so it only has two over-
loads: one that takes an object and one that takes a number. Calling pickCard with any other parameter
types would cause an error.
Go to TOC
111
typescript
A major part of software engineering is building components that not only have well-defined and consistent
APIs, but are also reusable. Components that are capable of working on the data of today as well as the
data of tomorrow will give you the most flexible capabilities for building up large software systems.
In languages like C# and Java, one of the main tools in the toolbox for creating reusable components is
generics, that is, being able to create a component that can work over a variety of types rather than a sin-
gle one. This allows users to consume these components and use their own types.
Without generics, we would either have to give the identity function a specific type:
Or, we could describe the identity function using the any type:
While using any is certainly generic in that it will cause the function to accept any and all types for the type
of arg , we actually are losing the information about what that type was when the function returns. If we
passed in a number, the only information we have is that any type could be returned.
Instead, we need a way of capturing the type of the argument in such a way that we can also use it to de-
note what is being returned. Here, we will use a type variable, a special kind of variable that works on types
rather than values.
We've now added a type variable T to the identity function. This T allows us to capture the type the user
provides (e.g. number ), so that we can use that information later. Here, we use T again as the return
type. On inspection, we can now see the same type is used for the argument and the return type. This al-
lows us to traffic that type information in one side of the function and out the other.
We say that this version of the identity function is generic, as it works over a range of types. Unlike us-
ing any , it's also just as precise (ie, it doesn't lose any information) as the first identity function that
used numbers for the argument and return type.
Once we've written the generic identity function, we can call it in one of two ways. The first way is to pass
all of the arguments, including the type argument, to the function:
112
Generics
Here we explicitly set T to be string as one of the arguments to the function call, denoted using the <>
around the arguments rather than () .
The second way is also perhaps the most common. Here we use type argument inference -- that is, we
want the compiler to set the value of T for us automatically based on the type of the argument we pass in:
Notice that we didn't have to explicitly pass the type in the angle brackets ( <> ); the compiler just looked at
the value "myString" , and set T to its type. While type argument inference can be a helpful tool to keep
code shorter and more readable, you may need to explicitly pass in the type arguments as we did in the
previous example when the compiler fails to infer the type, as may happen in more complex examples.
What if we want to also log the length of the argument arg to the console with each call? We might be
tempted to write this:
// @errors: 2339
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
When we do, the compiler will give us an error that we're using the .length member of arg , but nowhere
have we said that arg has this member. Remember, we said earlier that these type variables stand in for
any and all types, so someone using this function could have passed in a number instead, which does not
have a .length member.
113
typescript
Let's say that we've actually intended this function to work on arrays of T rather than T directly. Since
we're working with arrays, the .length member should be available. We can describe this just like we
would create arrays of other types:
You can read the type of loggingIdentity as "the generic function loggingIdentity takes a type para-
meter T , and an argument arg which is an array of T s, and returns an array of T s." If we passed in an
array of numbers, we'd get an array of numbers back out, as T would bind to number . This allows us to
use our generic type variable T as part of the types we're working with, rather than the whole type, giving
us greater flexibility.
You may already be familiar with this style of type from other languages. In the next section, we'll cover
how you can create your own generic types like Array<T> .
Generic Types
In previous sections, we created generic identity functions that worked over a range of types. In this
section, we'll explore the type of the functions themselves and how to create generic interfaces.
The type of generic functions is just like those of non-generic functions, with the type parameters listed
first, similarly to function declarations:
We could also have used a different name for the generic type parameter in the type, so long as the number
of type variables and how the type variables are used line up.
We can also write the generic type as a call signature of an object literal type:
114
Generics
Which leads us to writing our first generic interface. Let's take the object literal from the previous example
and move it to an interface:
interface GenericIdentityFn {
<T>(arg: T): T;
}
In a similar example, we may want to move the generic parameter to be a parameter of the whole
interface. This lets us see what type(s) we're generic over (e.g. Dictionary<string> rather than just
Dictionary ). This makes the type parameter visible to all the other members of the interface.
interface GenericIdentityFn<T> {
(arg: T): T;
}
Notice that our example has changed to be something slightly different. Instead of describing a generic
function, we now have a non-generic function signature that is a part of a generic type. When we use
GenericIdentityFn , we now will also need to specify the corresponding type argument (here: number ),
effectively locking in what the underlying call signature will use. Understanding when to put the type para-
meter directly on the call signature and when to put it on the interface itself will be helpful in describing
what aspects of a type are generic.
In addition to generic interfaces, we can also create generic classes. Note that it is not possible to create
generic enums and namespaces.
Generic Classes
A generic class has a similar shape to a generic interface. Generic classes have a generic type parameter list
in angle brackets ( <> ) following the name of the class.
// @strict: false
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
115
typescript
This is a pretty literal use of the GenericNumber class, but you may have noticed that nothing is restricting
it to only use the number type. We could have instead used string or even more complex objects.
// @strict: false
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
// ---cut---
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function (x, y) {
return x + y;
};
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
Just as with interface, putting the type parameter on the class itself lets us make sure all of the properties
of the class are working with the same type.
As we covered in our section on classes, a class has two sides to its type: the static side and the instance
side. Generic classes are only generic over their instance side rather than their static side, so when working
with classes, static members can not use the class's type parameter.
Generic Constraints
If you remember from an earlier example, you may sometimes want to write a generic function that works
on a set of types where you have some knowledge about what capabilities that set of types will have. In our
loggingIdentity example, we wanted to be able to access the .length property of arg , but the com-
piler could not prove that every type had a .length property, so it warns us that we can't make this
assumption.
// @errors: 2339
function loggingIdentity<T>(arg: T): T {
console.log(arg.length);
return arg;
}
Instead of working with any and all types, we'd like to constrain this function to work with any and all types
that also have the .length property. As long as the type has this member, we'll allow it, but it's required
to have at least this member. To do so, we must list our requirement as a constraint on what T can be.
To do so, we'll create an interface that describes our constraint. Here, we'll create an interface that has a
single .length property and then we'll use this interface and the extends keyword to denote our
constraint:
116
Generics
interface Lengthwise {
length: number;
}
Because the generic function is now constrained, it will no longer work over any and all types:
// @errors: 2345
interface Lengthwise {
length: number;
}
Instead, we need to pass in values whose type has all the required properties:
interface Lengthwise {
length: number;
}
// @errors: 2345
function getProperty<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a");
getProperty(x, "m");
117
typescript
A more advanced example uses the prototype property to infer and constrain relationships between the
constructor function and the instance side of class types.
// @strict: false
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
createInstance(Lion).keeper.nametag;
createInstance(Bee).keeper.hasMask;
Go to TOC
118
Interfaces
One of TypeScript's core principles is that type checking focuses on the shape that values have. This is
sometimes called "duck typing" or "structural subtyping". In TypeScript, interfaces fill the role of naming
these types, and are a powerful way of defining contracts within your code as well as contracts with code
outside of your project.
The type checker checks the call to printLabel . The printLabel function has a single parameter that re-
quires that the object passed in has a property called label of type string . Notice that our object actual-
ly has more properties than this, but the compiler only checks that at least the ones required are present
and match the types required. There are some cases where TypeScript isn't as lenient, which we'll cover in a
bit.
We can write the same example again, this time using an interface to describe the requirement of having
the label property that is a string:
interface LabeledValue {
label: string;
}
The interface LabeledValue is a name we can now use to describe the requirement in the previous exam-
ple. It still represents having a single property called label that is of type string . Notice we didn't have
to explicitly say that the object we pass to printLabel implements this interface like we might have to in
other languages. Here, it's only the shape that matters. If the object we pass to the function meets the re-
quirements listed, then it's allowed.
It's worth pointing out that the type checker does not require that these properties come in any sort of or-
der, only that the properties the interface requires are present and have the required type.
119
typescript
Optional Properties
Not all properties of an interface may be required. Some exist under certain conditions or may not be there
at all. These optional properties are popular when creating patterns like "option bags" where you pass an
object to a function that only has a couple of properties filled in.
interface SquareConfig {
color?: string;
width?: number;
}
Interfaces with optional properties are written similar to other interfaces, with each optional property denot-
ed by a ? at the end of the property name in the declaration.
The advantage of optional properties is that you can describe these possibly available properties while still
also preventing use of properties that are not part of the interface. For example, had we mistyped the name
of the color property in createSquare , we would get an error message letting us know:
// @errors: 2551
interface SquareConfig {
color?: string;
width?: number;
}
120
Interfaces
Readonly properties
Some properties should only be modifiable when an object is first created. You can specify this by putting
readonly before the name of the property:
interface Point {
readonly x: number;
readonly y: number;
}
You can construct a Point by assigning an object literal. After the assignment, x and y can't be
changed.
// @errors: 2540
interface Point {
readonly x: number;
readonly y: number;
}
// ---cut---
let p1: Point = { x: 10, y: 20 };
p1.x = 5; // error!
TypeScript comes with a ReadonlyArray<T> type that is the same as Array<T> with all mutating methods
removed, so you can make sure you don't change your arrays after creation:
On the last line of the snippet you can see that even assigning the entire ReadonlyArray back to a normal
array is illegal. You can still override it with a type assertion, though:
a = ro as number[];
readonly vs const
The easiest way to remember whether to use readonly or const is to ask whether you're using it on a
variable or a property. Variables use const whereas properties use readonly .
121
typescript
However, combining the two naively would allow an error to sneak in. For example, taking our last example
using createSquare :
Notice the given argument to createSquare is spelled colour instead of color . In plain JavaScript, this
sort of thing fails silently.
You could argue that this program is correctly typed, since the width properties are compatible, there's no
color property present, and the extra colour property is insignificant.
However, TypeScript takes the stance that there's probably a bug in this code. Object literals get special
treatment and undergo excess property checking when assigning them to other variables, or passing them
as arguments. If an object literal has any properties that the "target type" doesn't have, you'll get an error:
Getting around these checks is actually really simple. The easiest method is to just use a type assertion:
122
Interfaces
}
// ---cut---
let mySquare = createSquare({ width: 100, opacity: 0.5 } as SquareConfig);
However, a better approach might be to add a string index signature if you're sure that the object can have
some extra properties that are used in some special way. If SquareConfig can have color and width
properties with the above types, but could also have any number of other properties, then we could define it
like so:
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
We'll discuss index signatures in a bit, but here we're saying a SquareConfig can have any number of
properties, and as long as they aren't color or width , their types don't matter.
One final way to get around these checks, which might be a bit surprising, is to assign the object to another
variable: Since squareOptions won't undergo excess property checks, the compiler won't give you an
error.
interface SquareConfig {
color?: string;
width?: number;
[propName: string]: any;
}
The above workaround will work as long as you have a common property between squareOptions and
SquareConfig . In this example, it was the property width . It will however, fail if the variable does not
have any common object property. For example:
// @errors: 2559
interface SquareConfig {
color?: string;
width?: number;
}
123
typescript
// ---cut---
let squareOptions = { colour: "red" };
let mySquare = createSquare(squareOptions);
Keep in mind that for simple code like above, you probably shouldn't be trying to "get around" these checks.
For more complex object literals that have methods and hold state, you might need to keep these tech-
niques in mind, but a majority of excess property errors are actually bugs. That means if you're running into
excess property checking problems for something like option bags, you might need to revise some of your
type declarations. In this instance, if it's okay to pass an object with both a color or colour property to
createSquare , you should fix up the definition of SquareConfig to reflect that.
Function Types
Interfaces are capable of describing the wide range of shapes that JavaScript objects can take. In addition
to describing an object with properties, interfaces are also capable of describing function types.
To describe a function type with an interface, we give the interface a call signature. This is like a function
declaration with only the parameter list and return type given. Each parameter in the parameter list re-
quires both name and type.
interface SearchFunc {
(source: string, subString: string): boolean;
}
Once defined, we can use this function type interface like we would other interfaces. Here, we show how
you can create a variable of a function type and assign it a function value of the same type.
interface SearchFunc {
(source: string, subString: string): boolean;
}
// ---cut---
let mySearch: SearchFunc;
For function types to correctly type check, the names of the parameters do not need to match. We could
have, for example, written the above example like this:
interface SearchFunc {
(source: string, subString: string): boolean;
}
// ---cut---
let mySearch: SearchFunc;
124
Interfaces
Function parameters are checked one at a time, with the type in each corresponding parameter position
checked against each other. If you do not want to specify types at all, TypeScript's contextual typing can in-
fer the argument types since the function value is assigned directly to a variable of type SearchFunc . Here,
also, the return type of our function expression is implied by the values it returns (here false and true ).
interface SearchFunc {
(source: string, subString: string): boolean;
}
// ---cut---
let mySearch: SearchFunc;
Had the function expression returned numbers or strings, the type checker would have made an error that
indicates return type doesn't match the return type described in the SearchFunc interface.
// @errors: 2322
interface SearchFunc {
(source: string, subString: string): boolean;
}
// ---cut---
let mySearch: SearchFunc;
Indexable Types
Similarly to how we can use interfaces to describe function types, we can also describe types that we can
"index into" like a[10] , or ageMap["daniel"] . Indexable types have an index signature that describes
the types we can use to index into the object, along with the corresponding return types when indexing.
interface StringArray {
[index: number]: string;
}
Above, we have a StringArray interface that has an index signature. This index signature states that
when a StringArray is indexed with a number , it will return a string .
There are four types of supported index signatures: string, number, symbol and template strings. It is pos-
sible to support many types of indexers, but the type returned from a numeric indexer must be a subtype of
the type returned from the string indexer.
125
typescript
This is because when indexing with a number , JavaScript will actually convert that to a string before in-
dexing into an object. That means that indexing with 100 (a number ) is the same thing as indexing with
"100" (a string ), so the two need to be consistent.
// @errors: 2413
// @strictPropertyInitialization: false
interface Animal {
name: string;
}
// Error: indexing with a numeric string might get you a completely separate type
of Animal!
interface NotOkay {
[x: number]: Animal;
[x: string]: Dog;
}
While string index signatures are a powerful way to describe the "dictionary" pattern, they also enforce that
all properties match their return type. This is because a string index declares that obj.property is also
available as obj["property"] . In the following example, name 's type does not match the string index's
type, and the type checker gives an error:
// @errors: 2411
interface NumberDictionary {
[index: string]: number;
However, properties of different types are acceptable if the index signature is a union of the property types:
interface NumberOrStringDictionary {
[index: string]: number | string;
Finally, you can make index signatures readonly in order to prevent assignment to their indices:
// @errors: 2542
interface ReadonlyStringArray {
readonly [index: number]: string;
}
126
Interfaces
// @errors: 2339
interface HeadersResponse {
"content-type": string,
date: string,
"content-length": string
Class Types
Implementing an interface
One of the most common uses of interfaces in languages like C# and Java, that of explicitly enforcing that a
class meets a particular contract, is also possible in TypeScript.
interface ClockInterface {
currentTime: Date;
}
You can also describe methods in an interface that are implemented in the class, as we do with setTime in
the below example:
// @strictPropertyInitialization: false
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
127
typescript
}
constructor(h: number, m: number) {}
}
Interfaces describe the public side of the class, rather than both the public and private side. This prohibits
you from using them to check that a class also has particular types for the private side of the class instance.
This is because when a class implements an interface, only the instance side of the class is checked. Since
the constructor sits in the static side, it is not included in this check.
Instead, you would need to work with the static side of the class directly. In this example, we define two in-
terfaces, ClockConstructor for the constructor and ClockInterface for the instance methods. Then, for
convenience, we define a constructor function createClock that creates instances of the type that is
passed to it:
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick(): void;
}
function createClock(
ctor: ClockConstructor,
hour: number,
minute: number
): ClockInterface {
return new ctor(hour, minute);
}
128
Interfaces
// @strictPropertyInitialization: false
// @noImplicitAny: false
interface ClockConstructor {
new (hour: number, minute: number): ClockInterface;
}
interface ClockInterface {
tick(): void;
}
Extending Interfaces
Like classes, interfaces can extend each other. This allows you to copy the members of one interface into
another, which gives you more flexibility in how you separate your interfaces into reusable components.
interface Shape {
color: string;
}
An interface can extend multiple interfaces, creating a combination of all of the interfaces.
interface Shape {
color: string;
}
129
typescript
interface PenStroke {
penWidth: number;
}
Hybrid Types
As we mentioned earlier, interfaces can describe the rich types present in real world JavaScript. Because of
JavaScript's dynamic and flexible nature, you may occasionally encounter an object that works as a combi-
nation of some of the types described above.
One such example is an object that acts as both a function and an object, with additional properties:
interface Counter {
(start: number): string;
interval: number;
reset(): void;
}
let c = getCounter();
c(10);
c.reset();
c.interval = 5.0;
When interacting with 3rd-party JavaScript, you may need to use patterns like the above to fully describe
the shape of the type.
This is useful when you have a large inheritance hierarchy, but want to specify that your code works with
only subclasses that have certain properties. The subclasses don't have to be related besides inheriting from
the base class. For example:
130
Interfaces
In the above example, SelectableControl contains all of the members of Control , including the private
state property. Since state is a private member it is only possible for descendants of Control to imple-
ment SelectableControl . This is because only descendants of Control will have a state private mem-
ber that originates in the same declaration, which is a requirement for private members to be compatible.
Within the Control class it is possible to access the state private member through an instance of
SelectableControl . Effectively, a SelectableControl acts like a Control that is known to have a se‐
lect method. The Button and TextBox classes are subtypes of SelectableControl (because they both
inherit from Control and have a select method). The ImageControl class has its own state private
member rather than extending Control , so it cannot implement SelectableControl .
Go to TOC
131
typescript
A literal is a more concrete sub-type of a collective type. What this means is that "Hello World" is a
string , but a string is not "Hello World" inside the type system.
There are three sets of literal types available in TypeScript today: strings, numbers, and booleans; by using
literal types you can allow an exact value which a string, number, or boolean must have.
Literal Narrowing
When you declare a variable via var or let , you are telling the compiler that there is the chance that this
variable will change its contents. In contrast, using const to declare a variable will inform TypeScript that
this object will never change.
// On the other hand, a let can change, and so the compiler declares it a string
let hiWorld = "Hi World";
The process of going from an infinite number of potential cases (there is an infinite number of possible
string values) to a smaller, finite number of potential case (in helloWorld 's case: 1) is called narrowing.
// @errors: 2345
type Easing = "ease-in" | "ease-out" | "ease-in-out";
class UIElement {
animate(dx: number, dy: number, easing: Easing) {
if (easing === "ease-in") {
// ...
} else if (easing === "ease-out") {
} else if (easing === "ease-in-out") {
} else {
// It's possible that someone could reach this
// by ignoring your types though.
}
}
}
You can pass any of the three allowed strings, but any other string will give the error
132
Literal Types
String literal types can be used in the same way to distinguish overloads:
function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
return (Math.floor(Math.random() * 6) + 1) as 1 | 2 | 3 | 4 | 5 | 6;
}
interface ValidationSuccess {
isValid: true;
reason: null;
}
interface ValidationFailure {
isValid: false;
reason: string;
}
Go to TOC
133
typescript
So far, the handbook has covered types which are atomic objects. However, as you model more types you
find yourself looking for tools which let you compose or combine existing types instead of creating them
from scratch.
Intersection and Union types are one of the ways in which you can compose types.
Union Types
Occasionally, you'll run into a library that expects a parameter to be either a number or a string . For in-
stance, take the following function:
/**
* Takes a string and adds "padding" to the left.
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/
function padLeft(value: string, padding: any) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${typeof padding}'.`);
}
The problem with padLeft in the above example is that its padding parameter is typed as any . That
means that we can call it with an argument that's neither a number nor a string , but TypeScript will be
okay with it.
In traditional object-oriented code, we might abstract over the two types by creating a hierarchy of types.
While this is much more explicit, it's also a little bit overkill. One of the nice things about the original version
of padLeft was that we were able to just pass in primitives. That meant that usage was simple and con-
cise. This new approach also wouldn't help if we were just trying to use a function that already exists
elsewhere.
Instead of any , we can use a union type for the padding parameter:
// @errors: 2345
/**
* Takes a string and adds "padding" to the left.
* If 'padding' is a string, then 'padding' is appended to the left side.
* If 'padding' is a number, then that number of spaces is added to the left side.
*/
function padLeft(value: string, padding: string | number) {
// ...
134
Unions and Intersection Types
A union type describes a value that can be one of several types. We use the vertical bar ( | ) to separate
each type, so number | string | boolean is the type of a value that can be a number , a string , or a
boolean .
// @errors: 2339
interface Bird {
fly(): void;
layEggs(): void;
}
interface Fish {
swim(): void;
layEggs(): void;
}
Union types can be a bit tricky here, but it just takes a bit of intuition to get used to. If a value has the type
A | B , we only know for certain that it has members that both A and B have. In this example, Bird has
a member named fly . We can't be sure whether a variable typed as Bird | Fish has a fly method. If
the variable is really a Fish at runtime, then calling pet.fly() will fail.
Discriminating Unions
A common technique for working with unions is to have a single field which uses literal types which you can
use to let TypeScript narrow down the possible current type. For example, we're going to create a union of
three types which have a single shared field.
type NetworkLoadingState = {
state: "loading";
};
type NetworkFailedState = {
state: "failed";
code: number;
};
type NetworkSuccessState = {
135
typescript
state: "success";
response: {
title: string;
duration: number;
summary: string;
};
};
All of the above types have a field named state , and then they also have their own fields:
code response
Given the state field is common in every type inside NetworkState - it is safe for your code to access
without an existence check.
With state as a literal type, you can compare the value of state to the equivalent string and TypeScript
will know which type is currently being used.
In this case, you can use a switch statement to narrow down which type is represented at runtime:
// @errors: 2339
type NetworkLoadingState = {
state: "loading";
};
type NetworkFailedState = {
state: "failed";
code: number;
};
type NetworkSuccessState = {
state: "success";
response: {
title: string;
duration: number;
summary: string;
};
};
// ---cut---
type NetworkState =
| NetworkLoadingState
136
Unions and Intersection Types
| NetworkFailedState
| NetworkSuccessState;
// @errors: 2366
type NetworkLoadingState = { state: "loading" };
type NetworkFailedState = { state: "failed"; code: number };
type NetworkSuccessState = {
state: "success";
response: {
title: string;
duration: number;
summary: string;
};
};
// ---cut---
type NetworkFromCachedState = {
state: "from_cache";
id: string;
response: NetworkSuccessState["response"];
};
type NetworkState =
| NetworkLoadingState
| NetworkFailedState
| NetworkSuccessState
| NetworkFromCachedState;
137
typescript
case "success":
return "got response";
}
}
There are two ways to do this. The first is to turn on strictNullChecks and specify a return type:
// @errors: 2366
type NetworkLoadingState = { state: "loading" };
type NetworkFailedState = { state: "failed"; code: number };
type NetworkSuccessState = { state: "success" };
type NetworkFromCachedState = { state: "from_cache" };
type NetworkState =
| NetworkLoadingState
| NetworkFailedState
| NetworkSuccessState
| NetworkFromCachedState;
// ---cut---
function logger(s: NetworkState): string {
switch (s.state) {
case "loading":
return "loading request";
case "failed":
return `failed with code ${s.code}`;
case "success":
return "got response";
}
}
Because the switch is no longer exhaustive, TypeScript is aware that the function could sometimes return
undefined . If you have an explicit return type string , then you will get an error that the return type is
actually string | undefined . However, this method is quite subtle and, besides, strictNullChecks
does not always work with old code.
The second method uses the never type that the compiler uses to check for exhaustiveness:
// @errors: 2345
type NetworkLoadingState = { state: "loading" };
type NetworkFailedState = { state: "failed"; code: number };
type NetworkSuccessState = { state: "success" };
type NetworkFromCachedState = { state: "from_cache" };
type NetworkState =
| NetworkLoadingState
| NetworkFailedState
| NetworkSuccessState
| NetworkFromCachedState;
// ---cut---
function assertNever(x: never): never {
throw new Error("Unexpected object: " + x);
}
138
Unions and Intersection Types
Here, assertNever checks that s is of type never — the type that's left after all other cases have been
removed. If you forget a case, then s will have a real type and you will get a type error. This method re-
quires you to define an extra function, but it's much more obvious when you forget it because the error
message includes the missing type name.
Intersection Types
Intersection types are closely related to union types, but they are used very differently. An intersection type
combines multiple types into one. This allows you to add together existing types to get a single type that
has all the features you need. For example, Person & Serializable & Loggable is a type which is all of
Person and Serializable and Loggable . That means an object of this type will have all members of all
three types.
For example, if you had networking requests with consistent error handling then you could separate out the
error handling into its own type which is merged with types which correspond to a single response type.
interface ErrorHandling {
success: boolean;
error?: { message: string };
}
interface ArtworksData {
artworks: { title: string }[];
}
interface ArtistsData {
artists: { name: string }[];
}
console.log(response.artists);
};
Go to TOC
139
typescript
Each and every value in JavaScript has a set of behaviors you can observe from running different
operations. That sounds abstract, but as a quick example, consider some operations we might run on a vari-
able named message .
// Calling 'message'
message();
If we break this down, the first runnable line of code accesses a property called toLowerCase and then
calls it. The second one tries to call message directly.
But assuming we don't know the value of message - and that's pretty common - we can't reliably say what
results we'll get from trying to run any of this code. The behavior of each operation depends entirely on
what value we had in the first place.
Is message callable?
Does it have a property called toLowerCase on it?
If it does, is toLowerCase even callable?
If both of these values are callable, what do they return?
The answers to these questions are usually things we keep in our heads when we write JavaScript, and we
have to hope we got all the details right.
As you can probably guess, if we try to run message.toLowerCase() , we'll get the same string only in low-
er-case.
What about that second line of code? If you're familiar with JavaScript, you'll know this fails with an
exception:
When we run our code, the way that our JavaScript runtime chooses what to do is by figuring out the type
of the value - what sorts of behaviors and capabilities it has. That's part of what that TypeError is alluding
to - it's saying that the string "Hello World!" cannot be called as a function.
For some values, such as the primitives string and number , we can identify their type at runtime using
the typeof operator. But for other things like functions, there's no corresponding runtime mechanism to
identify their types. For example, consider this function:
140
The Basics
function fn(x) {
return x.flip();
}
We can observe by reading the code that this function will only work if given an object with a callable flip
property, but JavaScript doesn't surface this information in a way that we can check while the code is run-
ning. The only way in pure JavaScript to tell what fn does with a particular value is to call it and see what
happens. This kind of behavior makes it hard to predict what code will do before it runs, which means it's
harder to know what your code is going to do while you're writing it.
Seen in this way, a type is the concept of describing which values can be passed to fn and which will
crash. JavaScript only truly provides dynamic typing - running the code to see what happens.
The alternative is to use a static type system to make predictions about what code is expected before it
runs.
Static type-checking
Think back to that TypeError we got earlier from trying to call a string as a function. Most people don't
like to get any sorts of errors when running their code - those are considered bugs! And when we write new
code, we try our best to avoid introducing new bugs.
If we add just a bit of code, save our file, re-run the code, and immediately see the error, we might be able
to isolate the problem quickly; but that's not always the case. We might not have tested the feature thor-
oughly enough, so we might never actually run into a potential error that would be thrown! Or if we were
lucky enough to witness the error, we might have ended up doing large refactorings and adding a lot of dif-
ferent code that we're forced to dig through.
Ideally, we could have a tool that helps us find these bugs before our code runs. That's what a static type-
checker like TypeScript does. Static types systems describe the shapes and behaviors of what our values
will be when we run our programs. A type-checker like TypeScript uses that information and tells us when
things might be going off the rails.
// @errors: 2349
const message = "hello!";
message();
Running that last sample with TypeScript will give us an error message before we run the code in the first
place.
Non-exception Failures
So far we've been discussing certain things like runtime errors - cases where the JavaScript runtime tells us
that it thinks something is nonsensical. Those cases come up because the ECMAScript specification has ex-
plicit instructions on how the language should behave when it runs into something unexpected.
141
typescript
For example, the specification says that trying to call something that isn't callable should throw an error.
Maybe that sounds like "obvious behavior", but you could imagine that accessing a property that doesn't ex-
ist on an object should throw an error too. Instead, JavaScript gives us different behavior and returns the
value undefined :
const user = {
name: "Daniel",
age: 26,
};
Ultimately, a static type system has to make the call over what code should be flagged as an error in its
system, even if it's "valid" JavaScript that won't immediately throw an error. In TypeScript, the following
code produces an error about location not being defined:
// @errors: 2339
const user = {
name: "Daniel",
age: 26,
};
user.location;
While sometimes that implies a trade-off in what you can express, the intent is to catch legitimate bugs in
our programs. And TypeScript catches a lot of legitimate bugs.
// @noErrors
const announcement = "Hello World!";
uncalled functions,
// @noUnusedLocals
// @errors: 2365
function flipCoin() {
// Meant to be Math.random()
return Math.random < 0.5;
}
// @errors: 2367
const value = Math.random() < 0.5 ? "a" : "b";
if (value !== "a") {
// ...
142
The Basics
The type-checker has information to check things like whether we're accessing the right properties on vari-
ables and other properties. Once it has that information, it can also start suggesting which properties you
might want to use.
That means TypeScript can be leveraged for editing code too, and the core type-checker can provide error
messages and code completion as you type in the editor. That's part of what people often refer to when
they talk about tooling in TypeScript.
// @noErrors
// @esModuleInterop
import express from "express";
const app = express();
app.listen(3000);
TypeScript takes tooling seriously, and that goes beyond completions and errors as you type. An editor that
supports TypeScript can deliver "quick fixes" to automatically fix errors, refactorings to easily re-organize
code, and useful navigation features for jumping to definitions of a variable, or finding all references to a
given variable. All of this is built on top of the type-checker and is fully cross-platform, so it's likely that
your favorite editor has TypeScript support available.
This installs the TypeScript Compiler tsc globally. You can use npx or similar tools if you'd prefer to
run tsc from a local node_modules package instead.
Now let's move to an empty folder and try writing our first TypeScript program: hello.ts :
143
typescript
Notice there are no frills here; this "hello world" program looks identical to what you'd write for a "hello
world" program in JavaScript. And now let's type-check it by running the command tsc which was installed
for us by the typescript package.
tsc hello.ts
Tada!
Wait, "tada" what exactly? We ran tsc and nothing happened! Well, there were no type errors, so we
didn't get any output in our console since there was nothing to report.
But check again - we got some file output instead. If we look in our current directory, we'll see a hello.js
file next to hello.ts . That's the output from our hello.ts file after tsc compiles or transforms it into a
plain JavaScript file. And if we check the contents, we'll see what TypeScript spits out after it processes a
.ts file:
In this case, there was very little for TypeScript to transform, so it looks identical to what we wrote. The
compiler tries to emit clean readable code that looks like something a person would write. While that's not
always so easy, TypeScript indents consistently, is mindful of when our code spans across different lines of
code, and tries to keep comments around.
// @noErrors
// This is an industrial-grade general-purpose greeter function:
function greet(person, date) {
console.log(`Hello ${person}, today is ${date}!`);
}
greet("Brendan");
If we run tsc hello.ts again, notice that we get an error on the command line!
TypeScript is telling us we forgot to pass an argument to the greet function, and rightfully so. So far we've
only written standard JavaScript, and yet type-checking was still able to find problems with our code.
Thanks TypeScript!
144
The Basics
To reiterate from earlier, type-checking code limits the sorts of programs you can run, and so there's a
tradeoff on what sorts of things a type-checker finds acceptable. Most of the time that's okay, but there are
scenarios where those checks get in the way. For example, imagine yourself migrating JavaScript code over
to TypeScript and introducing type-checking errors. Eventually you'll get around to cleaning things up for
the type-checker, but that original JavaScript code was already working! Why should converting it over to
TypeScript stop you from running it?
So TypeScript doesn't get in your way. Of course, over time, you may want to be a bit more defensive
against mistakes, and make TypeScript act a bit more strictly. In that case, you can use the noEmitOn‐
Error compiler option. Try changing your hello.ts file and running tsc with that flag:
Explicit Types
Up until now, we haven't told TypeScript what person or date are. Let's edit the code to tell TypeScript
that person is a string , and that date should be a Date object. We'll also use the toDateString()
method on date .
What we did was add type annotations on person and date to describe what types of values greet can
be called with. You can read that signature as " greet takes a person of type string , and a date of
type Date ".
With this, TypeScript can tell us about other cases where greet might have been called incorrectly. For
example...
// @errors: 2345
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
greet("Maddison", Date());
Perhaps surprisingly, calling Date() in JavaScript returns a string . On the other hand, constructing a
Date with new Date() actually gives us what we were expecting.
145
typescript
Keep in mind, we don't always have to write explicit type annotations. In many cases, TypeScript can even
just infer (or "figure out") the types for us even if we omit them.
Even though we didn't tell TypeScript that msg had the type string it was able to figure that out. That's a
feature, and it's best not to add annotations when the type system would end up inferring the same type
anyway.
Note: The message bubble inside the previous code sample is what your editor would show if you had
hovered over the word.
Erased Types
Let's take a look at what happens when we compile the above function greet with tsc to output
JavaScript:
// @showEmit
// @target: es5
function greet(person: string, date: Date) {
console.log(`Hello ${person}, today is ${date.toDateString()}!`);
}
More on that second point later, but let's now focus on that first point. Type annotations aren't part of
JavaScript (or ECMAScript to be pedantic), so there really aren't any browsers or other runtimes that can
just run TypeScript unmodified. That's why TypeScript needs a compiler in the first place - it needs some
way to strip out or transform any TypeScript-specific code so that you can run it. Most TypeScript-specific
code gets erased away, and likewise, here our type annotations were completely erased.
Remember: Type annotations never change the runtime behavior of your program.
Downleveling
One other difference from the above was that our template string was rewritten from
146
The Basics
to
Template strings are a feature from a version of ECMAScript called ECMAScript 2015 (a.k.a. ECMAScript 6,
ES2015, ES6, etc. - don't ask). TypeScript has the ability to rewrite code from newer versions of ECMAScript
to older ones such as ECMAScript 3 or ECMAScript 5 (a.k.a. ES3 and ES5). This process of moving from a
newer or "higher" version of ECMAScript down to an older or "lower" one is sometimes called downleveling.
By default TypeScript targets ES3, an extremely old version of ECMAScript. We could have chosen some-
thing a little bit more recent by using the target option. Running with --target es2015 changes
TypeScript to target ECMAScript 2015, meaning code should be able to run wherever ECMAScript 2015 is
supported. So running tsc --target es2015 hello.ts gives us the following output:
While the default target is ES3, the great majority of current browsers support ES2015. Most develop-
ers can therefore safely specify ES2015 or above as a target, unless compatibility with certain ancient
browsers is important.
Strictness
Different users come to TypeScript looking for different things in a type-checker. Some people are looking
for a more loose opt-in experience which can help validate only some parts of their program, and still have
decent tooling. This is the default experience with TypeScript, where types are optional, inference takes the
most lenient types, and there's no checking for potentially null / undefined values. Much like how tsc
emits in the face of errors, these defaults are put in place to stay out of your way. If you're migrating exist-
ing JavaScript, that might be a desirable first step.
In contrast, a lot of users prefer to have TypeScript validate as much as it can straight away, and that's why
the language provides strictness settings as well. These strictness settings turn static type-checking from a
switch (either your code is checked or not) into something closer to a dial. The further you turn this dial up,
the more TypeScript will check for you. This can require a little extra work, but generally speaking it pays
for itself in the long run, and enables more thorough checks and more accurate tooling. When possible, a
new codebase should always turn these strictness checks on.
TypeScript has several type-checking strictness flags that can be turned on or off, and all of our examples
will be written with all of them enabled unless otherwise stated. The strict flag in the CLI, or "strict":
true in a tsconfig.json toggles them all on simultaneously, but we can opt out of them individually. The
two biggest ones you should know about are noImplicitAny and strictNullChecks .
147
typescript
noImplicitAny
Recall that in some places, TypeScript doesn't try to infer types for us and instead falls back to the most le-
nient type: any . This isn't the worst thing that can happen - after all, falling back to any is just the plain
JavaScript experience anyway.
However, using any often defeats the purpose of using TypeScript in the first place. The more typed your
program is, the more validation and tooling you'll get, meaning you'll run into fewer bugs as you code.
Turning on the noImplicitAny flag will issue an error on any variables whose type is implicitly inferred as
any .
strictNullChecks
By default, values like null and undefined are assignable to any other type. This can make writing some
code easier, but forgetting to handle null and undefined is the cause of countless bugs in the world -
some consider it a billion dollar mistake! The strictNullChecks flag makes handling null and unde‐
fined more explicit, and spares us from worrying about whether we forgot to handle null and unde‐
fined .
Go to TOC
148
Classes
Background Reading:
Classes (MDN)
TypeScript offers full support for the class keyword introduced in ES2015.
As with other JavaScript language features, TypeScript adds type annotations and other syntax to allow you
to express relationships between classes and other types.
Class Members
Here's the most basic class - an empty one:
class Point {}
This class isn't very useful yet, so let's start adding some members.
Fields
A field declaration creates a public writeable property on a class:
// @strictPropertyInitialization: false
class Point {
x: number;
y: number;
}
As with other locations, the type annotation is optional, but will be an implicit any if not specified.
Fields can also have initializers; these will run automatically when the class is instantiated:
class Point {
x = 0;
y = 0;
}
Just like with const , let , and var , the initializer of a class property will be used to infer its type:
// @errors: 2322
class Point {
x = 0;
y = 0;
}
149
typescript
// ---cut---
const pt = new Point();
pt.x = "0";
--strictPropertyInitialization
The strictPropertyInitialization setting controls whether class fields need to be initialized in the
constructor.
// @errors: 2564
class BadGreeter {
name: string;
}
class GoodGreeter {
name: string;
constructor() {
this.name = "hello";
}
}
Note that the field needs to be initialized in the constructor itself. TypeScript does not analyze methods you
invoke from the constructor to detect initializations, because a derived class might override those methods
and fail to initialize the members.
If you intend to definitely initialize a field through means other than the constructor (for example, maybe an
external library is filling in part of your class for you), you can use the definite assignment assertion opera-
tor, ! :
class OKGreeter {
// Not initialized, but no error
name!: string;
}
readonly
Fields may be prefixed with the readonly modifier. This prevents assignments to the field outside of the
constructor.
constructor(otherName?: string) {
if (otherName !== undefined) {
this.name = otherName;
}
}
err() {
this.name = "not ok";
}
}
const g = new Greeter();
g.name = "also not ok";
150
Classes
Constructors
Background Reading:
Constructor (MDN)
Class constructors are very similar to functions. You can add parameters with type annotations, default val-
ues, and overloads:
class Point {
x: number;
y: number;
class Point {
// Overloads
constructor(x: number, y: string);
constructor(s: string);
constructor(xs: any, y?: any) {
// TBD
}
}
There are just a few differences between class constructor signatures and function signatures:
Constructors can't have type parameters - these belong on the outer class declaration, which we'll learn
about later
Constructors can't have return type annotations - the class instance type is always what's returned
Super Calls
Just as in JavaScript, if you have a base class, you'll need to call super(); in your constructor body before
using any this. members:
// @errors: 17009
class Base {
k = 4;
}
151
typescript
Forgetting to call super is an easy mistake to make in JavaScript, but TypeScript will tell you when it's
necessary.
Methods
Background Reading:
Method definitions
A function property on a class is called a method. Methods can use all the same type annotations as func-
tions and constructors:
class Point {
x = 10;
y = 10;
Other than the standard type annotations, TypeScript doesn't add anything else new to methods.
Note that inside a method body, it is still mandatory to access fields and other methods via this. . An un-
qualified name in a method body will always refer to something in the enclosing scope:
// @errors: 2322
let x: number = 0;
class C {
x: string = "hello";
m() {
// This is trying to modify 'x' from line 1, not the class property
x = "world";
}
}
Getters / Setters
Classes can also have accessors:
class C {
_length = 0;
get length() {
return this._length;
}
set length(value) {
this._length = value;
}
}
152
Classes
Note that a field-backed get/set pair with no extra logic is very rarely useful in JavaScript. It's fine to
expose public fields if you don't need to add additional logic during the get/set operations.
Since TypeScript 4.3, it is possible to have accessors with different types for getting and setting.
class Thing {
_size = 0;
if (!Number.isFinite(num)) {
this._size = 0;
return;
}
this._size = num;
}
}
Index Signatures
Classes can declare index signatures; these work the same as Index Signatures for other object types:
class MyClass {
[s: string]: boolean | ((s: string) => boolean);
check(s: string) {
return this[s] as boolean;
}
}
Because the index signature type needs to also capture the types of methods, it's not easy to usefully use
these types. Generally it's better to store indexed data in another place instead of on the class instance
itself.
Class Heritage
Like other languages with object-oriented features, classes in JavaScript can inherit from base classes.
153
typescript
implements Clauses
You can use an implements clause to check that a class satisfies a particular interface . An error will be
issued if a class fails to correctly implement it:
// @errors: 2420
interface Pingable {
ping(): void;
}
Cautions
It's important to understand that an implements clause is only a check that the class can be treated as the
interface type. It doesn't change the type of the class or its methods at all. A common source of error is to
assume that an implements clause will change the class type - it doesn't!
// @errors: 7006
interface Checkable {
check(name: string): boolean;
}
In this example, we perhaps expected that s 's type would be influenced by the name: string parameter
of check . It is not - implements clauses don't change how the class body is checked or its type inferred.
Similarly, implementing an interface with an optional property doesn't create that property:
// @errors: 2339
interface A {
x: number;
y?: number;
}
class C implements A {
x = 0;
154
Classes
}
const c = new C();
c.y = 10;
extends Clauses
Background Reading:
extends keyword (MDN)
Classes may extend from a base class. A derived class has all the properties and methods of its base
class, and also define additional members.
class Animal {
move() {
console.log("Moving along!");
}
}
Overriding Methods
Background Reading:
super keyword (MDN)
A derived class can also override a base class field or property. You can use the super. syntax to access
base class methods. Note that because JavaScript classes are a simple lookup object, there is no notion of a
"super field".
TypeScript enforces that a derived class is always a subtype of its base class.
class Base {
greet() {
console.log("Hello, world!");
}
}
155
typescript
It's important that a derived class follow its base class contract. Remember that it's very common (and al-
ways legal!) to refer to a derived class instance through a base class reference:
class Base {
greet() {
console.log("Hello, world!");
}
}
declare const d: Base;
// ---cut---
// Alias the derived instance through a base class reference
const b: Base = d;
// No problem
b.greet();
// @errors: 2416
class Base {
greet() {
console.log("Hello, world!");
}
}
If we compiled this code despite the error, this sample would then crash:
156
Classes
When target >= ES2022 or useDefineForClassFields is true , class fields are initialized after the par-
ent class constructor completes, overwriting any value set by the parent class. This can be a problem when
you only want to re-declare a more accurate type for an inherited field. To handle these cases, you can
write declare to indicate to TypeScript that there should be no runtime effect for this field declaration.
interface Animal {
dateOfBirth: any;
}
class AnimalHouse {
resident: Animal;
constructor(animal: Animal) {
this.resident = animal;
}
}
Initialization Order
The order that JavaScript classes initialize can be surprising in some cases. Let's consider this code:
class Base {
name = "base";
constructor() {
console.log("My name is " + this.name);
}
}
157
typescript
This means that the base class constructor saw its own value for name during its own constructor, because
the derived class field initializations hadn't run yet.
Note: If you don't plan to inherit from built-in types like Array , Error , Map , etc. or your compila-
tion target is explicitly set to ES6 / ES2015 or above, you may skip this section
In ES2015, constructors which return an object implicitly substitute the value of this for any callers of
super(...) . It is necessary for generated constructor code to capture any potential return value of
super(...) and replace it with this .
As a result, subclassing Error , Array , and others may no longer work as expected. This is due to the fact
that constructor functions for Error , Array , and the like use ECMAScript 6's new.target to adjust the
prototype chain; however, there is no way to ensure a value for new.target when invoking a constructor
in ECMAScript 5. Other downlevel compilers generally have the same limitation by default.
methods may be undefined on objects returned by constructing these subclasses, so calling sayHello
will result in an error.
instanceof will be broken between instances of the subclass and their instances, so (new
MsgError()) instanceof MsgError will return false .
As a recommendation, you can manually adjust the prototype immediately after any super(...) calls.
sayHello() {
return "hello " + this.message;
}
}
158
Classes
However, any subclass of MsgError will have to manually set the prototype as well. For runtimes that don't
support Object.setPrototypeOf , you may instead be able to use __proto__ .
Unfortunately, these workarounds will not work on Internet Explorer 10 and prior. One can manually copy
methods from the prototype onto the instance itself (i.e. MsgError.prototype onto this ), but the proto-
type chain itself cannot be fixed.
Member Visibility
You can use TypeScript to control whether certain methods or properties are visible to code outside the
class.
public
The default visibility of class members is public . A public member can be accessed anywhere:
class Greeter {
public greet() {
console.log("hi!");
}
}
const g = new Greeter();
g.greet();
Because public is already the default visibility modifier, you don't ever need to write it on a class member,
but might choose to do so for style/readability reasons.
protected
protected members are only visible to subclasses of the class they're declared in.
// @errors: 2445
class Greeter {
public greet() {
console.log("Hello, " + this.getName());
}
protected getName() {
return "hi";
}
}
159
typescript
Derived classes need to follow their base class contracts, but may choose to expose a subtype of base class
with more capabilities. This includes making protected members public :
class Base {
protected m = 10;
}
class Derived extends Base {
// No modifier, so default is 'public'
m = 15;
}
const d = new Derived();
console.log(d.m); // OK
Note that Derived was already able to freely read and write m , so this doesn't meaningfully alter the "se-
curity" of this situation. The main thing to note here is that in the derived class, we need to be careful to re-
peat the protected modifier if this exposure isn't intentional.
Different OOP languages disagree about whether it's legal to access a protected member through a base
class reference:
// @errors: 2446
class Base {
protected x: number = 1;
}
class Derived1 extends Base {
protected x: number = 5;
}
class Derived2 extends Base {
f1(other: Derived2) {
other.x = 10;
}
f2(other: Base) {
other.x = 10;
}
}
Java, for example, considers this to be legal. On the other hand, C# and C++ chose that this code should
be illegal.
TypeScript sides with C# and C++ here, because accessing x in Derived2 should only be legal from
Derived2 's subclasses, and Derived1 isn't one of them. Moreover, if accessing x through a Derived1
reference is illegal (which it certainly should be!), then accessing it through a base class reference should
never improve the situation.
See also Why Can’t I Access A Protected Member From A Derived Class? which explains more of C#'s
reasoning.
private
private is like protected , but doesn't allow access to the member even from subclasses:
160
Classes
// @errors: 2341
class Base {
private x = 0;
}
const b = new Base();
// Can't access from outside the class
console.log(b.x);
// @errors: 2341
class Base {
private x = 0;
}
// ---cut---
class Derived extends Base {
showX() {
// Can't access in subclasses
console.log(this.x);
}
}
Because private members aren't visible to derived classes, a derived class can't increase its visibility:
// @errors: 2415
class Base {
private x = 0;
}
class Derived extends Base {
x = 1;
}
Different OOP languages disagree about whether different instances of the same class may access each oth-
ers' private members. While languages like Java, C#, C++, Swift, and PHP allow this, Ruby does not.
class A {
private x = 10;
public sameAs(other: A) {
// No error
return other.x === this.x;
}
}
Caveats
Like other aspects of TypeScript's type system, private and protected are only enforced during type
checking.
This means that JavaScript runtime constructs like in or simple property lookup can still access a pri‐
vate or protected member:
class MySafe {
private secretKey = 12345;
}
161
typescript
// In a JavaScript file...
const s = new MySafe();
// Will print 12345
console.log(s.secretKey);
private also allows access using bracket notation during type checking. This makes private -declared
fields potentially easier to access for things like unit tests, with the drawback that these fields are soft pri-
vate and don't strictly enforce privacy.
// @errors: 2341
class MySafe {
private secretKey = 12345;
}
// OK
console.log(s["secretKey"]);
Unlike TypeScripts's private , JavaScript's private fields ( # ) remain private after compilation and do not
provide the previously mentioned escape hatches like bracket notation access, making them hard private.
class Dog {
#barkAmount = 0;
personality = "happy";
constructor() {}
}
// @target: esnext
// @showEmit
class Dog {
#barkAmount = 0;
personality = "happy";
constructor() {}
}
// @target: es2015
// @showEmit
class Dog {
#barkAmount = 0;
personality = "happy";
constructor() {}
}
If you need to protect values in your class from malicious actors, you should use mechanisms that offer
hard runtime privacy, such as closures, WeakMaps, or private fields. Note that these added privacy checks
during runtime could affect performance.
162
Classes
Static Members
Background Reading:
Static Members (MDN)
Classes may have static members. These members aren't associated with a particular instance of the
class. They can be accessed through the class constructor object itself:
class MyClass {
static x = 0;
static printX() {
console.log(MyClass.x);
}
}
console.log(MyClass.x);
MyClass.printX();
Static members can also use the same public , protected , and private visibility modifiers:
// @errors: 2341
class MyClass {
private static x = 0;
}
console.log(MyClass.x);
class Base {
static getGreeting() {
return "Hello world";
}
}
class Derived extends Base {
myGreeting = Derived.getGreeting();
}
// @errors: 2699
class S {
static name = "S!";
}
163
typescript
Those constructs only exist because those languages force all data and functions to be inside a class; be-
cause that restriction doesn't exist in TypeScript, there's no need for them. A class with only a single in-
stance is typically just represented as a normal object in JavaScript/TypeScript.
For example, we don't need a "static class" syntax in TypeScript because a regular object (or even top-level
function) will do the job just as well:
// Preferred (alternative 1)
function doSomething() {}
// Preferred (alternative 2)
const MyHelperObject = {
dosomething() {},
};
get count() {
return Foo.#count;
}
static {
try {
const lastInstances = loadLastInstances();
Foo.#count += lastInstances.length;
}
catch {}
}
}
Generic Classes
Classes, much like interfaces, can be generic. When a generic class is instantiated with new , its type para-
meters are inferred the same way as in a function call:
class Box<Type> {
contents: Type;
constructor(value: Type) {
this.contents = value;
}
}
164
Classes
Classes can use generic constraints and defaults the same way as interfaces.
// @errors: 2302
class Box<Type> {
static defaultValue: Type;
}
Remember that types are always fully erased! At runtime, there's only one Box.defaultValue property
slot. This means that setting Box<string>.defaultValue (if that were possible) would also change
Box<number>.defaultValue - not good. The static members of a generic class can never refer to the
class's type parameters.
Background Reading:
this keyword (MDN)
It's important to remember that TypeScript doesn't change the runtime behavior of JavaScript, and that
JavaScript is somewhat famous for having some peculiar runtime behaviors.
class MyClass {
name = "MyClass";
getName() {
return this.name;
}
}
const c = new MyClass();
const obj = {
name: "obj",
getName: c.getName,
};
Long story short, by default, the value of this inside a function depends on how the function was called.
In this example, because the function was called through the obj reference, its value of this was obj
rather than the class instance.
This is rarely what you want to happen! TypeScript provides some ways to mitigate or prevent this kind of
error.
165
typescript
Arrow Functions
Background Reading:
Arrow functions (MDN)
If you have a function that will often be called in a way that loses its this context, it can make sense to
use an arrow function property instead of a method definition:
class MyClass {
name = "MyClass";
getName = () => {
return this.name;
};
}
const c = new MyClass();
const g = c.getName;
// Prints "MyClass" instead of crashing
console.log(g());
The this value is guaranteed to be correct at runtime, even for code not checked with TypeScript
This will use more memory, because each class instance will have its own copy of each function defined
this way
You can't use super.getName in a derived class, because there's no entry in the prototype chain to
fetch the base class method from
this parameters
In a method or function definition, an initial parameter named this has special meaning in TypeScript.
These parameters are erased during compilation:
// JavaScript output
function fn(x) {
/* ... */
}
TypeScript checks that calling a function with a this parameter is done so with a correct context. Instead
of using an arrow function, we can add a this parameter to method definitions to statically enforce that
the method is called correctly:
// @errors: 2684
class MyClass {
name = "MyClass";
166
Classes
getName(this: MyClass) {
return this.name;
}
}
const c = new MyClass();
// OK
c.getName();
This method makes the opposite trade-offs of the arrow function approach:
JavaScript callers might still use the class method incorrectly without realizing it
Only one function per class definition gets allocated, rather than one per class instance
Base method definitions can still be called via super .
this Types
In classes, a special type called this refers dynamically to the type of the current class. Let's see how this
is useful:
class Box {
contents: string = "";
set(value: string) {
// ^?
this.contents = value;
return this;
}
}
Here, TypeScript inferred the return type of set to be this , rather than Box . Now let's make a subclass
of Box :
class Box {
contents: string = "";
set(value: string) {
this.contents = value;
return this;
}
}
// ---cut---
class ClearableBox extends Box {
clear() {
this.contents = "";
}
}
167
typescript
class Box {
content: string = "";
sameAs(other: this) {
return other.content === this.content;
}
}
This is different from writing other: Box -- if you have a derived class, its sameAs method will now only
accept other instances of that same derived class:
// @errors: 2345
class Box {
content: string = "";
sameAs(other: this) {
return other.content === this.content;
}
}
// @strictPropertyInitialization: false
class FileSystemObject {
isFile(): this is FileRep {
return this instanceof FileRep;
}
isDirectory(): this is Directory {
return this instanceof Directory;
}
isNetworked(): this is Networked & this {
return this.networked;
}
constructor(public path: string, private networked: boolean) {}
}
interface Networked {
host: string;
}
168
Classes
if (fso.isFile()) {
fso.content;
// ^?
} else if (fso.isDirectory()) {
fso.children;
// ^?
} else if (fso.isNetworked()) {
fso.host;
// ^?
}
A common use-case for a this-based type guard is to allow for lazy validation of a particular field. For exam-
ple, this case removes an undefined from the value held inside box when hasValue has been verified to
be true:
class Box<T> {
value?: T;
box.value;
// ^?
if (box.hasValue()) {
box.value;
// ^?
}
Parameter Properties
TypeScript offers special syntax for turning a constructor parameter into a class property with the same
name and value. These are called parameter properties and are created by prefixing a constructor argument
with one of the visibility modifiers public , private , protected , or readonly . The resulting field gets
those modifier(s):
// @errors: 2341
class Params {
constructor(
public readonly x: number,
protected y: number,
private z: number
) {
// No body necessary
}
}
const a = new Params(1, 2, 3);
console.log(a.x);
// ^?
console.log(a.z);
169
typescript
Class Expressions
Background Reading:
Class expressions (MDN)
Class expressions are very similar to class declarations. The only real difference is that class expressions
don't need a name, though we can refer to them via whatever identifier they ended up bound to:
An abstract method or abstract field is one that hasn't had an implementation provided. These members
must exist inside an abstract class, which cannot be directly instantiated.
The role of abstract classes is to serve as a base class for subclasses which do implement all the abstract
members. When a class doesn't have any abstract members, it is said to be concrete.
// @errors: 2511
abstract class Base {
abstract getName(): string;
printName() {
console.log("Hello, " + this.getName());
}
}
We can't instantiate Base with new because it's abstract. Instead, we need to make a derived class and
implement the abstract members:
170
Classes
}
}
Notice that if we forget to implement the base class's abstract members, we'll get an error:
// @errors: 2515
abstract class Base {
abstract getName(): string;
printName() {}
}
// ---cut---
class Derived extends Base {
// forgot to do anything
}
// @errors: 2511
abstract class Base {
abstract getName(): string;
printName() {}
}
class Derived extends Base {
getName() {
return "";
}
}
// ---cut---
function greet(ctor: typeof Base) {
const instance = new ctor();
instance.printName();
}
TypeScript is correctly telling you that you're trying to instantiate an abstract class. After all, given the defi-
nition of greet , it's perfectly legal to write this code, which would end up constructing an abstract class:
Instead, you want to write a function that accepts something with a construct signature:
// @errors: 2345
abstract class Base {
abstract getName(): string;
printName() {}
}
class Derived extends Base {
171
typescript
getName() {
return "";
}
}
// ---cut---
function greet(ctor: new () => Base) {
const instance = new ctor();
instance.printName();
}
greet(Derived);
greet(Base);
Now TypeScript correctly tells you about which class constructor functions can be invoked - Derived can
because it's concrete, but Base cannot.
For example, these two classes can be used in place of each other because they're identical:
class Point1 {
x = 0;
y = 0;
}
class Point2 {
x = 0;
y = 0;
}
// OK
const p: Point1 = new Point2();
Similarly, subtype relationships between classes exist even if there's no explicit inheritance:
// @strict: false
class Person {
name: string;
age: number;
}
class Employee {
name: string;
age: number;
salary: number;
}
// OK
const p: Person = new Employee();
This sounds straightforward, but there are a few cases that seem stranger than others.
Empty classes have no members. In a structural type system, a type with no members is generally a super-
type of anything else. So if you write an empty class (don't!), anything can be used in place of it:
172
Classes
class Empty {}
// All OK!
fn(window);
fn({});
fn(fn);
Go to TOC
173
typescript
In this chapter, we'll cover some of the most common types of values you'll find in JavaScript code, and ex-
plain the corresponding ways to describe those types in TypeScript. This isn't an exhaustive list, and future
chapters will describe more ways to name and use other types.
Types can also appear in many more places than just type annotations. As we learn about the types them-
selves, we'll also learn about the places where we can refer to these types to form new constructs.
We'll start by reviewing the most basic and common types you might encounter when writing JavaScript or
TypeScript code. These will later form the core building blocks of more complex types.
The type names String , Number , and Boolean (starting with capital letters) are legal, but refer to
some special built-in types that will very rarely appear in your code. Always use string , number , or
boolean for types.
Arrays
To specify the type of an array like [1, 2, 3] , you can use the syntax number[] ; this syntax works for
any type (e.g. string[] is an array of strings, and so on). You may also see this written as
Array<number> , which means the same thing. We'll learn more about the syntax T<U> when we cover
generics.
any
TypeScript also has a special type, any , that you can use whenever you don't want a particular value to
cause typechecking errors.
174
Everyday Types
When a value is of type any , you can access any properties of it (which will in turn be of type any ), call it
like a function, assign it to (or from) a value of any type, or pretty much anything else that's syntactically
legal:
The any type is useful when you don't want to write out a long type just to convince TypeScript that a par-
ticular line of code is okay.
noImplicitAny
When you don't specify a type, and TypeScript can't infer it from context, the compiler will typically default
to any .
You usually want to avoid this, though, because any isn't type-checked. Use the compiler flag noImplici‐
tAny to flag any implicit any as an error.
TypeScript doesn't use "types on the left"-style declarations like int x = 0; Type annotations will al-
ways go after the thing being typed.
In most cases, though, this isn't needed. Wherever possible, TypeScript tries to automatically infer the
types in your code. For example, the type of a variable is inferred based on the type of its initializer:
For the most part you don't need to explicitly learn the rules of inference. If you're starting out, try using
fewer type annotations than you think - you might be surprised how few you need for TypeScript to fully un-
derstand what's going on.
175
typescript
Functions
Functions are the primary means of passing data around in JavaScript. TypeScript allows you to specify the
types of both the input and output values of functions.
When a parameter has a type annotation, arguments to that function will be checked:
// @errors: 2345
declare function greet(name: string): void;
// ---cut---
// Would be a runtime error if executed!
greet(42);
Even if you don't have type annotations on your parameters, TypeScript will still check that you
passed the right number of arguments.
Much like variable type annotations, you usually don't need a return type annotation because TypeScript will
infer the function's return type based on its return statements. The type annotation in the above example
doesn't change anything. Some codebases will explicitly specify a return type for documentation purposes,
to prevent accidental changes, or just for personal preference.
Anonymous Functions
Anonymous functions are a little bit different from function declarations. When a function appears in a place
where TypeScript can determine how it's going to be called, the parameters of that function are automati-
cally given types.
Here's an example:
176
Everyday Types
// @errors: 2551
// No type annotations here, but TypeScript can spot the bug
const names = ["Alice", "Bob", "Eve"];
Even though the parameter s didn't have a type annotation, TypeScript used the types of the forEach
function, along with the inferred type of the array, to determine the type s will have.
This process is called contextual typing because the context that the function occurred within informs what
type it should have.
Similar to the inference rules, you don't need to explicitly learn how this happens, but understanding that it
does happen can help you notice when type annotations aren't needed. Later, we'll see more examples of
how the context that a value occurs in can affect its type.
Object Types
Apart from primitives, the most common sort of type you'll encounter is an object type. This refers to any
JavaScript value with properties, which is almost all of them! To define an object type, we simply list its
properties and their types.
Here, we annotated the parameter with a type with two properties - x and y - which are both of type
number . You can use , or ; to separate the properties, and the last separator is optional either way.
The type part of each property is also optional. If you don't specify a type, it will be assumed to be any .
Optional Properties
Object types can also specify that some or all of their properties are optional. To do this, add a ? after the
property name:
177
typescript
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });
In JavaScript, if you access a property that doesn't exist, you'll get the value undefined rather than a run-
time error. Because of this, when you read from an optional property, you'll have to check for undefined
before using it.
// @errors: 2532
function printName(obj: { first: string; last?: string }) {
// Error - might crash if 'obj.last' wasn't provided!
console.log(obj.last.toUpperCase());
if (obj.last !== undefined) {
// OK
console.log(obj.last.toUpperCase());
}
Union Types
TypeScript's type system allows you to build new types out of existing ones using a large variety of opera-
tors. Now that we know how to write a few types, it's time to start combining them in interesting ways.
// @errors: 2345
function printId(id: number | string) {
console.log("Your ID is: " + id);
}
// OK
printId(101);
// OK
printId("202");
// Error
printId({ myID: 22342 });
TypeScript will only allow an operation if it is valid for every member of the union. For example, if you have
the union string | number , you can't use methods that are only available on string :
178
Everyday Types
// @errors: 2339
function printId(id: number | string) {
console.log(id.toUpperCase());
}
The solution is to narrow the union with code, the same as you would in JavaScript without type
annotations. Narrowing occurs when TypeScript can deduce a more specific type for a value based on the
structure of the code.
For example, TypeScript knows that only a string value will have a typeof value "string" :
Notice that in the else branch, we don't need to do anything special - if x wasn't a string[] , then it
must have been a string .
Sometimes you'll have a union where all the members have something in common. For example, both ar-
rays and strings have a slice method. If every member in a union has a property in common, you can use
that property without narrowing:
It might be confusing that a union of types appears to have the intersection of those types'
properties. This is not an accident - the name union comes from type theory. The union number | st
ring is composed by taking the union of the values from each type. Notice that given two sets with
corresponding facts about each set, only the intersection of those facts applies to the union of the sets
179
typescript
themselves. For example, if we had a room of tall people wearing hats, and another room of Spanish
speakers wearing hats, after combining those rooms, the only thing we know about every person is
that they must be wearing a hat.
Type Aliases
We've been using object types and union types by writing them directly in type annotations. This is conve-
nient, but it's common to want to use the same type more than once and refer to it by a single name.
A type alias is exactly that - a name for any type. The syntax for a type alias is:
type Point = {
x: number;
y: number;
};
You can actually use a type alias to give a name to any type at all, not just an object type. For example, a
type alias can name a union type:
Note that aliases are only aliases - you cannot use type aliases to create different/distinct "versions" of the
same type. When you use the alias, it's exactly as if you had written the aliased type. In other words, this
code might look illegal, but is OK according to TypeScript because both types are aliases for the same type:
Interfaces
An interface declaration is another way to name an object type:
180
Everyday Types
interface Point {
x: number;
y: number;
}
Just like when we used a type alias above, the example works just as if we had used an anonymous object
type. TypeScript is only concerned with the structure of the value we passed to printCoord - it only cares
that it has the expected properties. Being concerned only with the structure and capabilities of types is why
we call TypeScript a structurally typed type system.
Interface Type
Adding new fields to an existing interface A type cannot be changed after being created
181
typescript
You'll learn more about these concepts in later chapters, so don't worry if you don't understand all of these
right away.
Prior to TypeScript version 4.2, type alias names may appear in error messages, sometimes in place of
the equivalent anonymous type (which may or may not be desirable). Interfaces will always be named in
error messages.
Type aliases may not participate in declaration merging, but interfaces can.
Interfaces may only be used to declare the shapes of objects, not rename primitives.
Interface names will always appear in their original form in error messages, but only when they are used
by name.
For the most part, you can choose based on personal preference, and TypeScript will tell you if it needs
something to be the other kind of declaration. If you would like a heuristic, use interface until you need
to use features from type .
Type Assertions
Sometimes you will have information about the type of a value that TypeScript can't know about.
For example, if you're using document.getElementById , TypeScript only knows that this will return some
kind of HTMLElement , but you might know that your page will always have an HTMLCanvasElement with a
given ID.
In this situation, you can use a type assertion to specify a more specific type:
Like a type annotation, type assertions are removed by the compiler and won't affect the runtime behavior
of your code.
182
Everyday Types
You can also use the angle-bracket syntax (except if the code is in a .tsx file), which is equivalent:
Reminder: Because type assertions are removed at compile-time, there is no runtime checking associ-
ated with a type assertion. There won't be an exception or null generated if the type assertion is
wrong.
TypeScript only allows type assertions which convert to a more specific or less specific version of a type.
This rule prevents "impossible" coercions like:
// @errors: 2352
const x = "hello" as number;
Sometimes this rule can be too conservative and will disallow more complex coercions that might be valid.
If this happens, you can use two assertions, first to any (or unknown , which we'll introduce later), then to
the desired type:
Literal Types
In addition to the general types string and number , we can refer to specific strings and numbers in type
positions.
One way to think about this is to consider how JavaScript comes with different ways to declare a variable.
Both var and let allow for changing what is held inside the variable, and const does not. This is reflect-
ed in how TypeScript creates types for literals.
// @errors: 2322
let x: "hello" = "hello";
// OK
183
typescript
x = "hello";
// ...
x = "howdy";
It's not much use to have a variable that can only have one value!
But by combining literals into unions, you can express a much more useful concept - for example, functions
that only accept a certain set of known values:
// @errors: 2345
function printText(s: string, alignment: "left" | "right" | "center") {
// ...
}
printText("Hello, world", "left");
printText("G'day, mate", "centre");
// @errors: 2345
interface Options {
width: number;
}
function configure(x: Options | "auto") {
// ...
}
configure({ width: 100 });
configure("auto");
configure("automatic");
There's one more kind of literal type: boolean literals. There are only two boolean literal types, and as you
might guess, they are the types true and false . The type boolean itself is actually just an alias for the
union true | false .
Literal Inference
When you initialize a variable with an object, TypeScript assumes that the properties of that object might
change values later. For example, if you wrote code like this:
TypeScript doesn't assume the assignment of 1 to a field which previously had 0 is an error. Another way
of saying this is that obj.counter must have the type number , not 0 , because types are used to deter-
mine both reading and writing behavior.
184
Everyday Types
// @errors: 2345
declare function handleRequest(url: string, method: "GET" | "POST"): void;
// ---cut---
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
In the above example req.method is inferred to be string , not "GET" . Because code can be evaluated
between the creation of req and the call of handleRequest which could assign a new string like "GUESS"
to req.method , TypeScript considers this code to have an error.
1. You can change the inference by adding a type assertion in either location:
Change 1 means "I intend for req.method to always have the literal type "GET" ", preventing the possi-
ble assignment of "GUESS" to that field after. Change 2 means "I know for other reasons that re‐
q.method has the value "GET" ".
2. You can use as const to convert the entire object to be type literals:
The as const suffix acts like const but for the type system, ensuring that all properties are assigned the
literal type instead of a more general version like string or number .
TypeScript has two corresponding types by the same names. How these types behave depends on whether
you have the strictNullChecks option on.
strictNullChecks off
With strictNullChecks off, values that might be null or undefined can still be accessed normally, and
the values null and undefined can be assigned to a property of any type. This is similar to how lan-
guages without null checks (e.g. C#, Java) behave. The lack of checking for these values tends to be a ma-
jor source of bugs; we always recommend people turn strictNullChecks on if it's practical to do so in
their codebase.
185
typescript
strictNullChecks on
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. Just like checking for undefined before using an option-
al property, we can use narrowing to check for values that might be null :
Just like other type assertions, this doesn't change the runtime behavior of your code, so it's important to
only use ! when you know that the value can't be null or undefined .
Enums
Enums are a feature added to JavaScript by TypeScript which allows for describing a value which could be
one of a set of possible named constants. Unlike most TypeScript features, this is not a type-level addition
to JavaScript but something added to the language and runtime. Because of this, it's a feature which you
should know exists, but maybe hold off on using unless you are sure. You can read more about enums in
the Enum reference page.
bigint
From ES2020 onwards, there is a primitive in JavaScript used for very large integers, BigInt :
// @target: es2020
186
Everyday Types
You can learn more about BigInt in the TypeScript 3.2 release notes.
symbol
There is a primitive in JavaScript used to create a globally unique reference via the function Symbol() :
// @errors: 2367
const firstName = Symbol("name");
const secondName = Symbol("name");
Go to TOC
187
typescript
JavaScript has a long history of different ways to handle modularizing code. TypeScript having been around
since 2012, has implemented support for a lot of these formats, but over time the community and the
JavaScript specification has converged on a format called ES Modules (or ES6 modules). You might know it
as the import / export syntax.
ES Modules was added to the JavaScript spec in 2015, and by 2020 had broad support in most web
browsers and JavaScript runtimes.
For focus, the handbook will cover both ES Modules and its popular pre-cursor CommonJS module.exports
= syntax, and you can find information about the other module patterns in the reference section under
Modules.
Conversely, a file without any top-level import or export declarations is treated as a script whose contents
are available in the global scope (and therefore to modules as well).
Modules are executed within their own scope, not in the global scope. This means that variables, functions,
classes, etc. declared in a module are not visible outside the module unless they are explicitly exported us-
ing one of the export forms. Conversely, to consume a variable, function, class, interface, etc. exported
from a different module, it has to be imported using one of the import forms.
Non-modules
Before we start, it's important to understand what TypeScript considers a module. The JavaScript specifica-
tion declares that any JavaScript files without an export or top-level await should be considered a script
and not a module.
Inside a script file variables and types are declared to be in the shared global scope, and it's assumed that
you'll either use the outFile compiler option to join multiple input files into one output file, or use multiple
<script> tags in your HTML to load these files (in the correct order!).
If you have a file that doesn't currently have any import s or export s, but you want to be treated as a
module, add the line:
export {};
which will change the file to be a module exporting nothing. This syntax works regardless of your module
target.
188
Modules
Modules in TypeScript
Additional Reading:
Impatient JS (Modules)
MDN: JavaScript Modules
There are three main things to consider when writing module-based code in TypeScript:
ES Module Syntax
A file can declare a main export via export default :
// @filename: hello.ts
export default function helloWorld() {
console.log("Hello, world!");
}
// @filename: hello.ts
export default function helloWorld() {
console.log("Hello, world!");
}
// @filename: index.ts
// ---cut---
import helloWorld from "./hello.js";
helloWorld();
In addition to the default export, you can have more than one export of variables and functions via the ex‐
port by omitting default :
// @filename: maths.ts
export var pi = 3.14;
export let squareTwo = 1.41;
export const phi = 1.61;
189
typescript
// @filename: maths.ts
export var pi = 3.14;
export let squareTwo = 1.41;
export const phi = 1.61;
export class RandomNumberGenerator {}
export function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}
// @filename: app.ts
// ---cut---
import { pi, phi, absolute } from "./maths.js";
console.log(pi);
const absPhi = absolute(phi);
// ^?
// @filename: maths.ts
export var pi = 3.14;
// @filename: app.ts
// ---cut---
import { pi as π } from "./maths.js";
console.log(π);
// ^?
You can mix and match the above syntax into a single import :
// @filename: maths.ts
export const pi = 3.14;
export default class RandomNumberGenerator {}
// @filename: app.ts
import RandomNumberGenerator, { pi as π } from "./maths.js";
RandomNumberGenerator;
// ^?
console.log(π);
// ^?
You can take all of the exported objects and put them into a single namespace using * as name :
// @filename: maths.ts
export var pi = 3.14;
export let squareTwo = 1.41;
export const phi = 1.61;
190
Modules
console.log(math.pi);
const positivePhi = math.absolute(math.phi);
// ^?
You can import a file and not include any variables into your current module via import "./file" :
// @filename: maths.ts
export var pi = 3.14;
// ---cut---
// @filename: app.ts
import "./maths.js";
console.log("3.14");
In this case, the import does nothing. However, all of the code in maths.ts was evaluated, which could
trigger side-effects which affect other objects.
Types can be exported and imported using the same syntax as JavaScript values:
// @filename: animal.ts
export type Cat = { breed: string; yearOfBirth: number };
// @filename: app.ts
import { Cat, Dog } from "./animal.js";
type Animals = Cat | Dog;
TypeScript has extended the import syntax with two concepts for declaring an import of a type:
import type
// @filename: animal.ts
export type Cat = { breed: string; yearOfBirth: number };
export type Dog = { breeds: string[]; yearOfBirth: number };
export const createCatName = () => "fluffy";
// @filename: valid.ts
import type { Cat, Dog } from "./animal.js";
export type Animals = Cat | Dog;
// @filename: app.ts
// @errors: 1361
import type { createCatName } from "./animal.js";
const name = createCatName();
TypeScript 4.5 also allows for individual imports to be prefixed with type to indicate that the imported ref-
erence is a type:
191
typescript
// @filename: animal.ts
export type Cat = { breed: string; yearOfBirth: number };
export type Dog = { breeds: string[]; yearOfBirth: number };
export const createCatName = () => "fluffy";
// ---cut---
// @filename: app.ts
import { createCatName, type Cat, type Dog } from "./animal.js";
Together these allow a non-TypeScript transpiler like Babel, swc or esbuild to know what imports can be
safely removed.
TypeScript has ES Module syntax which directly correlates to a CommonJS and AMD require . Imports us-
ing ES Module are for most cases the same as the require from those environments, but this syntax en-
sures you have a 1 to 1 match in your TypeScript file with the CommonJS output:
You can learn more about this syntax in the modules reference page.
CommonJS Syntax
CommonJS is the format which most modules on npm are delivered in. Even if you are writing using the ES
Modules syntax above, having a brief understanding of how CommonJS syntax works will help you debug
easier.
Exporting
Identifiers are exported via setting the exports property on a global called module .
module.exports = {
pi: 3.14,
squareTwo: 1.41,
phi: 1.61,
absolute,
};
192
Modules
// @module: commonjs
// @filename: maths.ts
/// <reference types="node" />
function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}
module.exports = {
pi: 3.14,
squareTwo: 1.41,
phi: 1.61,
absolute,
};
// @filename: index.ts
// ---cut---
const maths = require("maths");
maths.pi;
// ^?
// @module: commonjs
// @filename: maths.ts
/// <reference types="node" />
function absolute(num: number) {
if (num < 0) return num * -1;
return num;
}
module.exports = {
pi: 3.14,
squareTwo: 1.41,
phi: 1.61,
absolute,
};
// @filename: index.ts
// ---cut---
const { squareTwo } = require("maths");
squareTwo;
// ^?
TypeScript includes two resolution strategies: Classic and Node. Classic, the default when the compiler op-
tion module is not commonjs , is included for backwards compatibility. The Node strategy replicates how
Node.js works in CommonJS mode, with additional checks for .ts and .d.ts .
193
typescript
There are many TSConfig flags which influence the module strategy within TypeScript: moduleResolution ,
baseUrl , paths , rootDirs .
For the full details on how these strategies work, you can consult the Module Resolution.
target which determines which JS features are downleveled (converted to run in older JavaScript run-
times) and which are left intact
module which determines what code is used for modules to interact with each other
Which target you use is determined by the features available in the JavaScript runtime you expect to run
the TypeScript code in. That could be: the oldest web browser you support, the lowest version of Node.js
you expect to run on or could come from unique constraints from your runtime - like Electron for example.
All communication between modules happens via a module loader, the compiler option module determines
which one is used. At runtime the module loader is responsible for locating and executing all dependencies
of a module before executing it.
For example, here is a TypeScript file using ES Modules syntax, showcasing a few different options for mod‐
ule :
// @filename: constants.ts
export const valueOfPi = 3.142;
// @filename: index.ts
// ---cut---
import { valueOfPi } from "./constants.js";
ES2020
// @showEmit
// @module: es2020
// @noErrors
import { valueOfPi } from "./constants.js";
CommonJS
// @showEmit
// @module: commonjs
// @noErrors
import { valueOfPi } from "./constants.js";
194
Modules
UMD
// @showEmit
// @module: umd
// @noErrors
import { valueOfPi } from "./constants.js";
You can see all of the available options and what their emitted JavaScript code looks like in the TSConfig
Reference for module .
TypeScript namespaces
TypeScript has its own module format called namespaces which pre-dates the ES Modules standard. This
syntax has a lot of useful features for creating complex definition files, and still sees active use in
DefinitelyTyped. While not deprecated, the majority of the features in namespaces exist in ES Modules and
we recommend you use that to align with JavaScript's direction. You can learn more about namespaces in
the namespaces reference page.
Go to TOC
195
typescript
Functions are the basic building block of any application, whether they're local functions, imported from an-
other module, or methods on a class. They're also values, and just like other values, TypeScript has many
ways to describe how functions can be called. Let's learn about how to write types that describe functions.
greeter(printToConsole);
The syntax (a: string) => void means "a function with one parameter, named a , of type string, that
doesn't have a return value". Just like with function declarations, if a parameter type isn't specified, it's im-
plicitly any .
Note that the parameter name is required. The function type (string) => void means "a function
with a parameter named string of type any "!
Call Signatures
In JavaScript, functions can have properties in addition to being callable. However, the function type expres-
sion syntax doesn't allow for declaring properties. If we want to describe something callable with properties,
we can write a call signature in an object type:
type DescribableFunction = {
description: string;
(someArg: number): boolean;
};
function doSomething(fn: DescribableFunction) {
console.log(fn.description + " returned " + fn(6));
}
196
More on Functions
Note that the syntax is slightly different compared to a function type expression - use : between the para-
meter list and the return type rather than => .
Construct Signatures
JavaScript functions can also be invoked with the new operator. TypeScript refers to these as constructors
because they usually create a new object. You can write a construct signature by adding the new keyword
in front of a call signature:
Some objects, like JavaScript's Date object, can be called with or without new . You can combine call and
construct signatures in the same type arbitrarily:
interface CallOrConstruct {
new (s: string): Date;
(n?: number): number;
}
Generic Functions
It's common to write a function where the types of the input relate to the type of the output, or where the
types of two inputs are related in some way. Let's consider for a moment a function that returns the first
element of an array:
This function does its job, but unfortunately has the return type any . It'd be better if the function returned
the type of the array element.
In TypeScript, generics are used when we want to describe a correspondence between two values. We do
this by declaring a type parameter in the function signature:
By adding a type parameter Type to this function and using it in two places, we've created a link between
the input of the function (the array) and the output (the return value). Now when we call it, a more specific
type comes out:
197
typescript
Inference
Note that we didn't have to specify Type in this sample. The type was inferred - chosen automatically - by
TypeScript.
We can use multiple type parameters as well. For example, a standalone version of map would look like
this:
// prettier-ignore
function map<Input, Output>(arr: Input[], func: (arg: Input) => Output): Output[]
{
return arr.map(func);
}
Note that in this example, TypeScript could infer both the type of the Input type parameter (from the giv-
en string array), as well as the Output type parameter based on the return value of the function expres-
sion ( number ).
Constraints
We've written some generic functions that can work on any kind of value. Sometimes we want to relate two
values, but can only operate on a certain subset of values. In this case, we can use a constraint to limit the
kinds of types that a type parameter can accept.
Let's write a function that returns the longer of two values. To do this, we need a length property that's a
number. We constrain the type parameter to that type by writing an extends clause:
198
More on Functions
There are a few interesting things to note in this example. We allowed TypeScript to infer the return type of
longest . Return type inference also works on generic functions.
Because we constrained Type to { length: number } , we were allowed to access the .length property
of the a and b parameters. Without the type constraint, we wouldn't be able to access those properties
because the values might have been some other type without a length property.
The types of longerArray and longerString were inferred based on the arguments. Remember, generics
are all about relating two or more values with the same type!
Finally, just as we'd like, the call to longest(10, 100) is rejected because the number type doesn't have
a .length property.
// @errors: 2322
function minimumLength<Type extends { length: number }>(
obj: Type,
minimum: number
): Type {
if (obj.length >= minimum) {
return obj;
} else {
return { length: minimum };
}
}
It might look like this function is OK - Type is constrained to { length: number } , and the function ei-
ther returns Type or a value matching that constraint. The problem is that the function promises to return
the same kind of object as was passed in, not just some object matching the constraint. If this code were
legal, you could write code that definitely wouldn't work:
199
typescript
// @errors: 2322
declare function combine<Type>(arr1: Type[], arr2: Type[]): Type[];
// ---cut---
const arr = combine([1, 2, 3], ["hello"]);
// a: number (good)
const a = firstElement1([1, 2, 3]);
// b: any (bad)
const b = firstElement2([1, 2, 3]);
These might seem identical at first glance, but firstElement1 is a much better way to write this function.
Its inferred return type is Type , but firstElement2 's inferred return type is any because TypeScript has
to resolve the arr[0] expression using the constraint type, rather than "waiting" to resolve the element
during a call.
Rule: When possible, use the type parameter itself rather than constraining it
200
More on Functions
arr: Type[],
func: Func
): Type[] {
return arr.filter(func);
}
We've created a type parameter Func that doesn't relate two values. That's always a red flag, because it
means callers wanting to specify type arguments have to manually specify an extra type argument for no
reason. Func doesn't do anything but make the function harder to read and reason about!
greet("world");
Remember, type parameters are for relating the types of multiple values. If a type parameter is only used
once in the function signature, it's not relating anything.
Rule: If a type parameter only appears in one location, strongly reconsider if you actually need it
Optional Parameters
Functions in JavaScript often take a variable number of arguments. For example, the toFixed method of
number takes an optional digit count:
201
typescript
Although the parameter is specified as type number , the x parameter will actually have the type number
| undefined because unspecified parameters in JavaScript get the value undefined .
Now in the body of f , x will have type number because any undefined argument will be replaced with
10 . Note that when a parameter is optional, callers can always pass undefined , as this simply simulates a
"missing" argument:
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
for (let i = 0; i < arr.length; i++) {
callback(arr[i], i);
}
}
What people usually intend when writing index? as an optional parameter is that they want both of these
calls to be legal:
// @errors: 2532
declare function myForEach(
arr: any[],
callback: (arg: any, index?: number) => void
): void;
// ---cut---
myForEach([1, 2, 3], (a) => console.log(a));
myForEach([1, 2, 3], (a, i) => console.log(a, i));
What this actually means is that callback might get invoked with one argument. In other words, the func-
tion definition says that the implementation might look like this:
202
More on Functions
// @errors: 2532
function myForEach(arr: any[], callback: (arg: any, index?: number) => void) {
for (let i = 0; i < arr.length; i++) {
// I don't feel like providing the index today
callback(arr[i]);
}
}
In turn, TypeScript will enforce this meaning and issue errors that aren't really possible:
// @errors: 2532
declare function myForEach(
arr: any[],
callback: (arg: any, index?: number) => void
): void;
// ---cut---
myForEach([1, 2, 3], (a, i) => {
console.log(i.toFixed());
});
In JavaScript, if you call a function with more arguments than there are parameters, the extra arguments
are simply ignored. TypeScript behaves the same way. Functions with fewer parameters (of the same types)
can always take the place of functions with more parameters.
When writing a function type for a callback, never write an optional parameter unless you intend to
call the function without passing that argument
Function Overloads
Some JavaScript functions can be called in a variety of argument counts and types. For example, you might
write a function to produce a Date that takes either a timestamp (one argument) or a month/day/year
specification (three arguments).
In TypeScript, we can specify a function that can be called in different ways by writing overload signatures.
To do this, write some number of function signatures (usually two or more), followed by the body of the
function:
// @errors: 2575
function makeDate(timestamp: number): Date;
function makeDate(m: number, d: number, y: number): Date;
function makeDate(mOrTimestamp: number, d?: number, y?: number): Date {
if (d !== undefined && y !== undefined) {
return new Date(y, mOrTimestamp, d);
} else {
return new Date(mOrTimestamp);
}
}
const d1 = makeDate(12345678);
const d2 = makeDate(5, 5, 5);
const d3 = makeDate(1, 3);
203
typescript
In this example, we wrote two overloads: one accepting one argument, and another accepting three argu-
ments. These first two signatures are called the overload signatures.
Then, we wrote a function implementation with a compatible signature. Functions have an implementation
signature, but this signature can't be called directly. Even though we wrote a function with two optional pa-
rameters after the required one, it can't be called with two parameters!
// @errors: 2554
function fn(x: string): void;
function fn() {
// ...
}
// Expected to be able to call with zero arguments
fn();
Again, the signature used to write the function body can't be "seen" from the outside.
The signature of the implementation is not visible from the outside. When writing an overloaded func-
tion, you should always have two or more signatures above the implementation of the function.
The implementation signature must also be compatible with the overload signatures. For example, these
functions have errors because the implementation signature doesn't match the overloads in a correct way:
// @errors: 2394
function fn(x: boolean): void;
// Argument type isn't right
function fn(x: string): void;
function fn(x: boolean) {}
// @errors: 2394
function fn(x: string): string;
// Return type isn't right
function fn(x: number): boolean;
function fn(x: string | number) {
return "oops";
}
204
More on Functions
This function is fine; we can invoke it with strings or arrays. However, we can't invoke it with a value that
might be a string or an array, because TypeScript can only resolve a function call to a single overload:
// @errors: 2769
declare function len(s: string): number;
declare function len(arr: any[]): number;
// ---cut---
len(""); // OK
len([0]); // OK
len(Math.random() > 0.5 ? "hello" : [0]);
Because both overloads have the same argument count and same return type, we can instead write a non-
overloaded version of the function:
This is much better! Callers can invoke this with either sort of value, and as an added bonus, we don't have
to figure out a correct implementation signature.
Always prefer parameters with union types instead of overloads when possible
const user = {
id: 123,
admin: false,
becomeAdmin: function () {
this.admin = true;
},
};
TypeScript understands that the function user.becomeAdmin has a corresponding this which is the outer
object user . this , heh, can be enough for a lot of cases, but there are a lot of cases where you need
more control over what object this represents. 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.
205
typescript
interface User {
id: number;
admin: boolean;
}
declare const getDB: () => DB;
// ---cut---
interface DB {
filterUsers(filter: (this: User) => boolean): User[];
}
const db = getDB();
const admins = db.filterUsers(function (this: User) {
return this.admin;
});
This pattern is common with callback-style APIs, where another object typically controls when your function
is called. Note that you need to use function and not arrow functions to get this behavior:
const db = getDB();
const admins = db.filterUsers(() => this.admin);
void
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:
In JavaScript, a function that doesn't return any value will implicitly return the value undefined . However,
void and undefined are not the same thing in TypeScript. There are further details at the end of this
chapter.
206
More on Functions
object
The special type object refers to any value that isn't a primitive ( string , number , bigint , boolean ,
symbol , null , or undefined ). This is different from the empty object type { } , and also different from
the global type Object . It's very likely you will never use Object .
Note that in JavaScript, function values are objects: They have properties, have Object.prototype in their
prototype chain, are instanceof Object , you can call Object.keys on them, and so on. For this reason,
function types are considered to be object s in TypeScript.
unknown
The unknown type represents any value. This is similar to the any type, but is safer because it's not legal
to do anything with an unknown value:
// @errors: 2571
function f1(a: any) {
a.b(); // OK
}
function f2(a: unknown) {
a.b();
}
This is useful when describing function types because you can describe functions that accept any value with-
out having any values in your function body.
Conversely, you can describe a function that returns a value of unknown type:
never
Some functions never return a value:
207
typescript
The never type represents values which are never observed. In a return type, this means that the function
throws an exception or terminates execution of the program.
never also appears when TypeScript determines there's nothing left in a union.
Function
The global type Function describes properties like bind , call , apply , and others present on all func-
tion values in JavaScript. It also has the special property that values of type Function can always be
called; these calls return any :
This is an untyped function call and is generally best avoided because of the unsafe any return type.
If you need to accept an arbitrary function but don't intend to call it, the type () => void is generally
safer.
Background Reading:
Rest Parameters
Spread Syntax
Rest Parameters
In addition to using optional parameters or overloads to make functions that can accept a variety of fixed
argument counts, we can also define functions that take an unbounded number of arguments using rest
parameters.
A rest parameter appears after all other parameters, and uses the ... syntax:
208
More on Functions
In TypeScript, the type annotation on these parameters is implicitly any[] instead of any , and any type
annotation given must be of the form Array<T> or T[] , or a tuple type (which we'll learn about later).
Rest Arguments
Conversely, we can provide a variable number of arguments from an array using the spread syntax. For ex-
ample, the push method of arrays takes any number of arguments:
Note that in general, TypeScript does not assume that arrays are immutable. This can lead to some surpris-
ing behavior:
// @errors: 2556
// Inferred type is number[] -- "an array with zero or more numbers",
// not specifically two numbers
const args = [8, 5];
const angle = Math.atan2(...args);
The best fix for this situation depends a bit on your code, but in general a const context is the most
straightforward solution:
Using rest arguments may require turning on downlevelIteration when targeting older runtimes.
Parameter Destructuring
Background Reading:
Destructuring Assignment
You can use parameter destructuring to conveniently unpack objects provided as an argument into one or
more local variables in the function body. In JavaScript, it looks like this:
function sum({ a, b, c }) {
console.log(a + b + c);
}
sum({ a: 10, b: 3, c: 9 });
The type annotation for the object goes after the destructuring syntax:
209
typescript
This can look a bit verbose, but you can use a named type here as well:
Assignability of Functions
Return type void
The void return type for functions can produce some unusual, but expected behavior.
Contextual typing with a return type of void does not force functions to not return something. Another
way to say this is a contextual function type with a void return type ( type vf = () => void ), when im-
plemented, can return any other value, but it will be ignored.
Thus, the following implementations of the type () => void are valid:
And when the return value of one of these functions is assigned to another variable, it will retain the type of
void :
const v2 = f2();
const v3 = f3();
This behavior exists so that the following code is valid even though Array.prototype.push returns a num-
ber and the Array.prototype.forEach method expects a function with a return type of void .
210
More on Functions
There is one other special case to be aware of, when a literal function definition has a void return type,
that function must not return anything.
v1 handbook
v2 handbook
FAQ - "Why are functions returning non-void assignable to function returning void?"
Go to TOC
211
typescript
If padding is a number , it will treat that as the number of spaces we want to prepend to input . If pad‐
ding is a string , it should just prepend padding to input . Let's try to implement the logic for when
padLeft is passed a number for padding .
// @errors: 2345
function padLeft(padding: number | string, input: string) {
return " ".repeat(padding) + input;
}
Uh-oh, we're getting an error on padding . TypeScript is warning us that adding a number | string to a
number might not give us what we want, and it's right. In other words, we haven't explicitly checked if
padding is a number first, nor are we handling the case where it's a string , so let's do exactly that.
If this mostly looks like uninteresting JavaScript code, that's sort of the point. Apart from the annotations
we put in place, this TypeScript code looks like JavaScript. The idea is that TypeScript's type system aims to
make it as easy as possible to write typical JavaScript code without bending over backwards to get type
safety.
While it might not look like much, there's actually a lot going under the covers here. Much like how
TypeScript analyzes runtime values using static types, it overlays type analysis on JavaScript's runtime con-
trol flow constructs like if/else , conditional ternaries, loops, truthiness checks, etc., which can all affect
those types.
Within our if check, TypeScript sees typeof padding === "number" and understands that as a special
form of code called a type guard. TypeScript follows possible paths of execution that our programs can take
to analyze the most specific possible type of a value at a given position. It looks at these special checks
(called type guards) and assignments, and the process of refining types to more specific types than de-
clared is called narrowing. In many editors we can observe these types as they change, and we'll even do
so in our examples.
212
Narrowing
"string"
"number"
"bigint"
"boolean"
"symbol"
"undefined"
"object"
"function"
Like we saw with padLeft , this operator comes up pretty often in a number of JavaScript libraries, and
TypeScript can understand it to narrow types in different branches.
In TypeScript, checking against the value returned by typeof is a type guard. Because TypeScript encodes
how typeof operates on different values, it knows about some of its quirks in JavaScript. For example, no-
tice that in the list above, typeof doesn't return the string null . Check out the following example:
// @errors: 2531
function printAll(strs: string | string[] | null) {
if (typeof strs === "object") {
for (const s of strs) {
console.log(s);
}
} else if (typeof strs === "string") {
console.log(strs);
} else {
// do nothing
}
}
In the printAll function, we try to check if strs is an object to see if it's an array type (now might be a
good time to reinforce that arrays are object types in JavaScript). But it turns out that in JavaScript, type‐
of null is actually "object" ! This is one of those unfortunate accidents of history.
Users with enough experience might not be surprised, but not everyone has run into this in JavaScript;
luckily, TypeScript lets us know that strs was only narrowed down to string[] | null instead of just
string[] .
This might be a good segue into what we'll call "truthiness" checking.
213
typescript
Truthiness narrowing
Truthiness might not be a word you'll find in the dictionary, but it's very much something you'll hear about
in JavaScript.
In JavaScript, we can use any expression in conditionals, && s, || s, if statements, Boolean negations
( ! ), and more. As an example, if statements don't expect their condition to always have the type bool‐
ean .
In JavaScript, constructs like if first "coerce" their conditions to boolean s to make sense of them, and
then choose their branches depending on whether the result is true or false . Values like
0
NaN
"" (the empty string)
0n (the bigint version of zero)
null
undefined
all coerce to false , and other values get coerced true . You can always coerce values to boolean s by
running them through the Boolean function, or by using the shorter double-Boolean negation. (The latter
has the advantage that TypeScript infers a narrow literal boolean type true , while inferring the first as
type boolean .)
It's fairly popular to leverage this behavior, especially for guarding against values like null or undefined .
As an example, let's try using it for our printAll function.
You'll notice that we've gotten rid of the error above by checking if strs is truthy. This at least prevents us
from dreaded errors when we run our code like:
214
Narrowing
Keep in mind though that truthiness checking on primitives can often be error prone. As an example, con-
sider a different attempt at writing printAll
We wrapped the entire body of the function in a truthy check, but this has a subtle downside: we may no
longer be handling the empty string case correctly.
TypeScript doesn't hurt us here at all, but this is behavior worth noting if you're less familiar with
JavaScript. TypeScript can often help you catch bugs early on, but if you choose to do nothing with a value,
there's only so much that it can do without being overly prescriptive. If you want, you can make sure you
handle situations like these with a linter.
One last word on narrowing by truthiness is that Boolean negations with ! filter out from negated
branches.
function multiplyAll(
values: number[] | undefined,
factor: number
): number[] | undefined {
if (!values) {
return values;
} else {
return values.map((x) => x * factor);
}
}
Equality narrowing
TypeScript also uses switch statements and equality checks like === , !== , == , and != to narrow
types. For example:
215
typescript
} else {
console.log(x);
// ^?
console.log(y);
// ^?
}
}
When we checked that x and y are both equal in the above example, TypeScript knew their types also
had to be equal. Since string is the only common type that both x and y could take on, TypeScript
knows that x and y must be a string in the first branch.
Checking against specific literal values (as opposed to variables) works also. In our section about truthiness
narrowing, we wrote a printAll function which was error-prone because it accidentally didn't handle emp-
ty strings properly. Instead we could have done a specific check to block out null s, and TypeScript still
correctly removes null from the type of strs .
JavaScript's looser equality checks with == and != also get narrowed correctly. If you're unfamiliar, check-
ing whether something == null actually not only checks whether it is specifically the value null - it also
checks whether it's potentially undefined . The same applies to == undefined : it checks whether a value
is either null or undefined .
interface Container {
value: number | null | undefined;
}
216
Narrowing
For example, with the code: "value" in x . where "value" is a string literal and x is a union type. The
"true" branch narrows x 's types which have either an optional or required property value , and the "false"
branch narrows to types which have an optional or missing property value .
return animal.fly();
}
To reiterate optional properties will exist in both sides for narrowing, for example a human could both swim
and fly (with the right equipment) and thus should show up in both sides of the in check:
instanceof narrowing
JavaScript has an operator for checking whether or not a value is an "instance" of another value. More
specifically, in JavaScript x instanceof Foo checks whether the prototype chain of x contains
Foo.prototype . While we won't dive deep here, and you'll see more of this when we get into classes, they
can still be useful for most values that can be constructed with new . As you might have guessed, in‐
stanceof is also a type guard, and TypeScript narrows in branches guarded by instanceof s.
217
typescript
Assignments
As we mentioned earlier, when we assign to any variable, TypeScript looks at the right side of the assign-
ment and narrows the left side appropriately.
console.log(x);
// ^?
x = "goodbye!";
console.log(x);
// ^?
Notice that each of these assignments is valid. Even though the observed type of x changed to number af-
ter our first assignment, we were still able to assign a string to x . This is because the declared type of
x - the type that x started with - is string | number , and assignability is always checked against the
declared type.
If we'd assigned a boolean to x , we'd have seen an error since that wasn't part of the declared type.
// @errors: 2322
let x = Math.random() < 0.5 ? 10 : "hello world!";
// ^?
x = 1;
console.log(x);
// ^?
x = true;
console.log(x);
// ^?
padLeft returns from within its first if block. TypeScript was able to analyze this code and see that the
rest of the body ( return padding + input; ) is unreachable in the case where padding is a number . As
a result, it was able to remove number from the type of padding (narrowing from string | number to
string ) for the rest of the function.
218
Narrowing
This analysis of code based on reachability is called control flow analysis, and TypeScript uses this flow
analysis to narrow types as it encounters type guards and assignments. When a variable is analyzed, con-
trol flow can split off and re-merge over and over again, and that variable can be observed to have a differ-
ent type at each point.
function example() {
let x: string | number | boolean;
console.log(x);
// ^?
return x;
// ^?
}
To define a user-defined type guard, we simply need to define a function whose return type is a type
predicate:
pet is Fish is our type predicate in this example. A predicate takes the form parameterName is Type ,
where parameterName must be the name of a parameter from the current function signature.
Any time isFish is called with some variable, TypeScript will narrow that variable to that specific type if
the original type is compatible.
219
typescript
// ---cut---
// Both calls to 'swim' and 'fly' are now okay.
let pet = getSmallPet();
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
Notice that TypeScript not only knows that pet is a Fish in the if branch; it also knows that in the
else branch, you don't have a Fish , so you must have a Bird .
You may use the type guard isFish to filter an array of Fish | Bird and obtain an array of Fish :
Discriminated unions
Most of the examples we've looked at so far have focused around narrowing single variables with simple
types like string , boolean , and number . While this is common, most of the time in JavaScript we'll be
dealing with slightly more complex structures.
For some motivation, let's imagine we're trying to encode shapes like circles and squares. Circles keep track
of their radiuses and squares keep track of their side lengths. We'll use a field called kind to tell which
shape we're dealing with. Here's a first attempt at defining Shape .
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}
Notice we're using a union of string literal types: "circle" and "square" to tell us whether we should
treat the shape as a circle or square respectively. By using "circle" | "square" instead of string , we
can avoid misspelling issues.
220
Narrowing
// @errors: 2367
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}
// ---cut---
function handleShape(shape: Shape) {
// oops!
if (shape.kind === "rect") {
// ...
}
}
We can write a getArea function that applies the right logic based on if it's dealing with a circle or square.
We'll first try dealing with circles.
// @errors: 2532
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}
// ---cut---
function getArea(shape: Shape) {
return Math.PI * shape.radius ** 2;
}
Under strictNullChecks that gives us an error - which is appropriate since radius might not be
defined. But what if we perform the appropriate checks on the kind property?
// @errors: 2532
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}
// ---cut---
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
}
}
Hmm, TypeScript still doesn't know what to do here. We've hit a point where we know more about our val-
ues than the type checker does. We could try to use a non-null assertion (a ! after shape.radius ) to say
that radius is definitely present.
interface Shape {
kind: "circle" | "square";
radius?: number;
sideLength?: number;
}
// ---cut---
221
typescript
But this doesn't feel ideal. We had to shout a bit at the type-checker with those non-null assertions ( ! ) to
convince it that shape.radius was defined, but those assertions are error-prone if we start to move code
around. Additionally, outside of strictNullChecks we're able to accidentally access any of those fields
anyway (since optional properties are just assumed to always be present when reading them). We can defi-
nitely do better.
The problem with this encoding of Shape is that the type-checker doesn't have any way to know whether
or not radius or sideLength are present based on the kind property. We need to communicate what
we know to the type checker. With that in mind, let's take another swing at defining Shape .
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
Here, we've properly separated Shape out into two types with different values for the kind property, but
radius and sideLength are declared as required properties in their respective types.
Let's see what happens here when we try to access the radius of a Shape .
// @errors: 2339
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
// ---cut---
function getArea(shape: Shape) {
return Math.PI * shape.radius ** 2;
}
222
Narrowing
Like with our first definition of Shape , this is still an error. When radius was optional, we got an error
(with strictNullChecks enabled) because TypeScript couldn't tell whether the property was present. Now
that Shape is a union, TypeScript is telling us that shape might be a Square , and Square s don't have
radius defined on them! Both interpretations are correct, but only the union encoding of Shape will cause
an error regardless of how strictNullChecks is configured.
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
// ---cut---
function getArea(shape: Shape) {
if (shape.kind === "circle") {
return Math.PI * shape.radius ** 2;
// ^?
}
}
That got rid of the error! When every type in a union contains a common property with literal types,
TypeScript considers that to be a discriminated union, and can narrow out the members of the union.
In this case, kind was that common property (which is what's considered a discriminant property of
Shape ). Checking whether the kind property was "circle" got rid of every type in Shape that didn't
have a kind property with the type "circle" . That narrowed shape down to the type Circle .
The same checking works with switch statements as well. Now we can try to write our complete getArea
without any pesky ! non-null assertions.
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
// ---cut---
function getArea(shape: Shape) {
switch (shape.kind) {
case "circle":
return Math.PI * shape.radius ** 2;
// ^?
223
typescript
case "square":
return shape.sideLength ** 2;
// ^?
}
}
The important thing here was the encoding of Shape . Communicating the right information to TypeScript -
that Circle and Square were really two separate types with specific kind fields - was crucial. Doing that
let us write type-safe TypeScript code that looks no different than the JavaScript we would've written other-
wise. From there, the type system was able to do the "right" thing and figure out the types in each branch
of our switch statement.
As an aside, try playing around with the above example and remove some of the return keywords.
You'll see that type-checking can help avoid bugs when accidentally falling through different clauses in
a switch statement.
Discriminated unions are useful for more than just talking about circles and squares. They're good for repre-
senting any sort of messaging scheme in JavaScript, like when sending messages over the network
(client/server communication), or encoding mutations in a state management framework.
Exhaustiveness checking
The never type is assignable to every type; however, no type is assignable to never (except never
itself). This means you can use narrowing and rely on never turning up to do exhaustive checking in a
switch statement.
For example, adding a default to our getArea function which tries to assign the shape to never will
raise when every possible case has not been handled.
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
// ---cut---
type Shape = Circle | Square;
224
Narrowing
Adding a new member to the Shape union, will cause a TypeScript error:
// @errors: 2322
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
// ---cut---
interface Triangle {
kind: "triangle";
sideLength: number;
}
Go to TOC
225
typescript
In JavaScript, the fundamental way that we group and pass around data is through objects. In TypeScript,
we represent those through object types.
interface Person {
// ^^^^^^
name: string;
age: number;
}
or a type alias.
type Person = {
// ^^^^^^
name: string;
age: number;
};
In all three examples above, we've written functions that take objects that contain the property name
(which must be a string ) and age (which must be a number ).
Property Modifiers
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.
Optional Properties
Much of the time, we'll find ourselves dealing with objects that might have a property set. In those cases,
we can mark those properties as optional by adding a question mark ( ? ) to the end of their names.
interface Shape {}
declare function getShape(): Shape;
// ---cut---
interface PaintOptions {
shape: Shape;
xPos?: number;
226
Object Types
// ^
yPos?: number;
// ^
}
In this example, both xPos and yPos are considered optional. We can choose to provide either of them,
so every call above to paintShape is valid. All optionality really says is that if the property is set, it better
have a specific type.
We can also read from those properties - but when we do under strictNullChecks , TypeScript will tell us
they're potentially undefined .
interface Shape {}
declare function getShape(): Shape;
interface PaintOptions {
shape: Shape;
xPos?: number;
yPos?: number;
}
// ---cut---
function paintShape(opts: PaintOptions) {
let xPos = opts.xPos;
// ^?
let yPos = opts.yPos;
// ^?
// ...
}
In JavaScript, even if the property has never been set, we can still access it - it's just going to give us the
value undefined . We can just handle undefined specially.
interface Shape {}
declare function getShape(): Shape;
interface PaintOptions {
shape: Shape;
xPos?: number;
yPos?: number;
}
// ---cut---
function paintShape(opts: PaintOptions) {
let xPos = opts.xPos === undefined ? 0 : opts.xPos;
// ^?
let yPos = opts.yPos === undefined ? 0 : opts.yPos;
227
typescript
// ^?
// ...
}
Note that this pattern of setting defaults for unspecified values is so common that JavaScript has syntax to
support it.
interface Shape {}
declare function getShape(): Shape;
interface PaintOptions {
shape: Shape;
xPos?: number;
yPos?: number;
}
// ---cut---
function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) {
console.log("x coordinate at", xPos);
// ^?
console.log("y coordinate at", yPos);
// ^?
// ...
}
Here we used a destructuring pattern for paintShape 's parameter, and provided default values for xPos
and yPos . Now xPos and yPos are both definitely present within the body of paintShape , but optional
for any callers to paintShape .
Note that there is currently no way to place type annotations within destructuring patterns. This is be-
cause the following syntax already means something different in JavaScript.
// @noImplicitAny: false
// @errors: 2552 2304
interface Shape {}
declare function render(x: unknown);
// ---cut---
function draw({ shape: Shape, xPos: number = 100 /*...*/ }) {
render(shape);
render(xPos);
}
In an object destructuring pattern, shape: Shape means "grab the property shape and redefine it locally
as a variable named Shape . Likewise xPos: number creates a variable named number whose value is
based on the parameter's xPos .
readonly Properties
Properties can also be marked as readonly for TypeScript. While it won't change any behavior at runtime,
a property marked as readonly can't be written to during type-checking.
228
Object Types
// @errors: 2540
interface SomeType {
readonly prop: string;
}
Using the readonly modifier doesn't necessarily imply that a value is totally immutable - or in other
words, that its internal contents can't be changed. It just means the property itself can't be re-written to.
// @errors: 2540
interface Home {
readonly resident: { name: string; age: number };
}
It's important to manage expectations of what readonly implies. It's useful to signal intent during devel-
opment time for TypeScript on how an object should be used. TypeScript doesn't factor in whether proper-
ties on two types are readonly when checking whether those types are compatible, so readonly proper-
ties can also change via aliasing.
interface Person {
name: string;
age: number;
}
interface ReadonlyPerson {
readonly name: string;
readonly age: number;
}
// works
let readonlyPerson: ReadonlyPerson = writablePerson;
229
typescript
Index Signatures
Sometimes you don't know all the names of a type's properties ahead of time, but you do know the shape
of the values.
In those cases you can use an index signature to describe the types of possible values, for example:
Above, we have a StringArray interface which has an index signature. This index signature states that
when a StringArray is indexed with a number , it will return a string .
Only some types are allowed for index signature properties: string , number , symbol , template string
patterns, and union types consisting only of these.
230
Object Types
Details
While string index signatures are a powerful way to describe the "dictionary" pattern, they also enforce that
all properties match their return type. This is because a string index declares that obj.property is also
available as obj["property"] . In the following example, name 's type does not match the string index's
type, and the type checker gives an error:
// @errors: 2411
// @errors: 2411
interface NumberDictionary {
[index: string]: number;
length: number; // ok
name: string;
}
However, properties of different types are acceptable if the index signature is a union of the property types:
interface NumberOrStringDictionary {
[index: string]: number | string;
length: number; // ok, length is a number
name: string; // ok, name is a string
}
Finally, you can make index signatures readonly in order to prevent assignment to their indices:
Extending Types
It's pretty common to have types that might be more specific versions of other types. For example, we
might have a BasicAddress type that describes the fields necessary for sending letters and packages in
the U.S.
interface BasicAddress {
name?: string;
street: string;
city: string;
country: string;
postalCode: string;
}
In some situations that's enough, but addresses often have a unit number associated with them if the build-
ing at an address has multiple units. We can then describe an AddressWithUnit .
231
typescript
interface AddressWithUnit {
name?: string;
unit: string;
//^^^^^^^^^^^^^
street: string;
city: string;
country: string;
postalCode: string;
}
This does the job, but the downside here is that we had to repeat all the other fields from BasicAddress
when our changes were purely additive. Instead, we can extend the original BasicAddress type and just
add the new fields that are unique to AddressWithUnit .
interface BasicAddress {
name?: string;
street: string;
city: string;
country: string;
postalCode: string;
}
The extends keyword on an interface allows us to effectively copy members from other named types,
and add whatever new members we want. This can be useful for cutting down the amount of type declara-
tion boilerplate we have to write, and for signaling intent that several different declarations of the same
property might be related. For example, AddressWithUnit didn't need to repeat the street property, and
because street originates from BasicAddress , a reader will know that those two types are related in
some way.
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
Intersection Types
interface s allowed us to build up new types from other types by extending them. TypeScript provides an-
other construct called intersection types that is mainly used to combine existing object types.
232
Object Types
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
Here, we've intersected Colorful and Circle to produce a new type that has all the members of
Colorful and Circle .
// @errors: 2345
interface Colorful {
color: string;
}
interface Circle {
radius: number;
}
// ---cut---
function draw(circle: Colorful & Circle) {
console.log(`Color was ${circle.color}`);
console.log(`Radius was ${circle.radius}`);
}
// okay
draw({ color: "blue", radius: 42 });
// oops
draw({ color: "red", raidus: 42 });
interface Box {
contents: any;
}
Right now, the contents property is typed as any , which works, but can lead to accidents down the line.
We could instead use unknown , but that would mean that in cases where we already know the type of
contents , we'd need to do precautionary checks, or use error-prone type assertions.
233
typescript
interface Box {
contents: unknown;
}
let x: Box = {
contents: "hello world",
};
One type safe approach would be to instead scaffold out different Box types for every type of contents .
// @errors: 2322
interface NumberBox {
contents: number;
}
interface StringBox {
contents: string;
}
interface BooleanBox {
contents: boolean;
}
But that means we'll have to create different functions, or overloads of functions, to operate on these types.
interface NumberBox {
contents: number;
}
interface StringBox {
contents: string;
}
interface BooleanBox {
contents: boolean;
}
// ---cut---
function setContents(box: StringBox, newContents: string): void;
function setContents(box: NumberBox, newContents: number): void;
function setContents(box: BooleanBox, newContents: boolean): void;
function setContents(box: { contents: any }, newContents: any) {
box.contents = newContents;
}
That's a lot of boilerplate. Moreover, we might later need to introduce new types and overloads. This is frus-
trating, since our box types and overloads are all effectively the same.
Instead, we can make a generic Box type which declares a type parameter.
234
Object Types
interface Box<Type> {
contents: Type;
}
You might read this as “A Box of Type is something whose contents have type Type ”. Later on, when
we refer to Box , we have to give a type argument in place of Type .
interface Box<Type> {
contents: Type;
}
// ---cut---
let box: Box<string>;
Think of Box as a template for a real type, where Type is a placeholder that will get replaced with some
other type. When TypeScript sees Box<string> , it will replace every instance of Type in Box<Type> with
string , and end up working with something like { contents: string } . In other words, Box<string>
and our earlier StringBox work identically.
interface Box<Type> {
contents: Type;
}
interface StringBox {
contents: string;
}
Box is reusable in that Type can be substituted with anything. That means that when we need a box for a
new type, we don't need to declare a new Box type at all (though we certainly could if we wanted to).
interface Box<Type> {
contents: Type;
}
interface Apple {
// ....
}
This also means that we can avoid overloads entirely by instead using generic functions.
interface Box<Type> {
contents: Type;
}
// ---cut---
235
typescript
It is worth noting that type aliases can also be generic. We could have defined our new Box<Type> inter-
face, which was:
interface Box<Type> {
contents: Type;
}
type Box<Type> = {
contents: Type;
};
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.
// @errors: 2575
type OrNull<Type> = Type | null;
It turns out we've been working with a type just like that throughout this handbook: the Array type.
Whenever we write out types like number[] or string[] , that's really just a shorthand for
Array<number> and Array<string> .
Much like the Box type above, Array itself is a generic type.
236
Object Types
// @noLib: true
interface Number {}
interface String {}
interface Boolean {}
interface Symbol {}
// ---cut---
interface Array<Type> {
/**
* Gets or sets the length of the array.
*/
length: number;
/**
* Removes the last element from an array and returns it.
*/
pop(): Type | undefined;
/**
* Appends new elements to an array, and returns the new length of the array.
*/
push(...items: Type[]): number;
// ...
}
Modern JavaScript also provides other data structures which are generic, like Map<K, V> , Set<T> , and
Promise<T> . All this really means is that because of how Map , Set , and Promise behave, they can work
with any sets of types.
// @errors: 2339
function doStuff(values: ReadonlyArray<string>) {
// We can read from 'values'...
const copy = values.slice();
console.log(`The first value is ${values[0]}`);
Much like the readonly modifier for properties, it's mainly a tool we can use for intent. When we see a
function that returns ReadonlyArray s, it tells us we're not meant to change the contents at all, and when
we see a function that consumes ReadonlyArray s, it tells us that we can pass any array into that function
without worrying that it will change its contents.
// @errors: 2693
new ReadonlyArray("red", "green", "blue");
237
typescript
Just as TypeScript provides a shorthand syntax for Array<Type> with Type[] , it also provides a shorthand
syntax for ReadonlyArray<Type> with readonly Type[] .
// @errors: 2339
function doStuff(values: readonly string[]) {
// ^^^^^^^^^^^^^^^^^
// We can read from 'values'...
const copy = values.slice();
console.log(`The first value is ${values[0]}`);
One last thing to note is that unlike the readonly property modifier, assignability isn't bidirectional be-
tween regular Array s and ReadonlyArray s.
// @errors: 4104
let x: readonly string[] = [];
let y: string[] = [];
x = y;
y = x;
Tuple Types
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.
Here, StringNumberPair is a tuple type of string and number . Like ReadonlyArray , it has no repre-
sentation at runtime, but is significant to TypeScript. To the type system, StringNumberPair describes ar-
rays whose 0 index contains a string and whose 1 index contains a number .
doSomething(["hello", 42]);
// @errors: 2493
function doSomething(pair: [string, number]) {
// ...
const c = pair[2];
}
238
Object Types
console.log(inputString);
// ^?
console.log(hash);
// ^?
}
Tuple types are useful in heavily convention-based APIs, where each element's meaning is "obvious".
This gives us flexibility in whatever we want to name our variables when we destructure them. In the
above example, we were able to name elements 0 and 1 to whatever we wanted.
However, since not every user holds the same view of what's obvious, it may be worth reconsidering
whether using objects with descriptive property names may be better for your API.
Other than those length checks, simple tuple types like these are equivalent to types which are versions of
Array s that declare properties for specific indexes, and that declare length with a numeric literal type.
interface StringNumberPair {
// specialized properties
length: 2;
0: string;
1: number;
Another thing you may be interested in is that 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.
239
typescript
StringNumberBooleans describes a tuple whose first two elements are string and number respec-
tively, but which may have any number of boolean s following.
StringBooleansNumber describes a tuple whose first element is string and then any number of
boolean s and ending with a number .
BooleansStringNumber describes a tuple whose starting elements are any number of boolean s and
ending with a string then a number .
A tuple with a rest element has no set "length" - it only has a set of well-known elements in different
positions.
Why might optional and rest elements be useful? Well, it allows TypeScript to correspond tuples with para-
meter lists. Tuples types can be used in rest parameters and arguments, so that the following:
This is handy when you want to take a variable number of arguments with a rest parameter, and you need a
minimum number of elements, but you don't want to introduce intermediate variables.
As you might expect, writing to any property of a readonly tuple isn't allowed in TypeScript.
// @errors: 2540
function doSomething(pair: readonly [string, number]) {
pair[0] = "hello!";
}
240
Object Types
Tuples tend to be created and left un-modified in most code, so annotating types as readonly tuples when
possible is a good default. This is also important given that array literals with const assertions will be in-
ferred with readonly tuple types.
// @errors: 2345
let point = [3, 4] as const;
distanceFromOrigin(point);
Here, distanceFromOrigin never modifies its elements, but expects a mutable tuple. Since point 's type
was inferred as readonly [3, 4] , it won't be compatible with [number, number] since that type can't
guarantee point 's elements won't be mutated.
Go to TOC
241
typescript
The most common kinds of errors that programmers write can be described as type errors: a certain kind of
value was used where a different kind of value was expected. This could be due to simple typos, a failure to
understand the API surface of a library, incorrect assumptions about runtime behavior, or other errors. The
goal of TypeScript is to be a static typechecker for JavaScript programs - in other words, a tool that runs
before your code runs (static) and ensures that the types of the program are correct (typechecked).
If you are coming to TypeScript without a JavaScript background, with the intention of TypeScript being
your first language, we recommend you first start reading the documentation on either the Microsoft Learn
JavaScript tutorial or read JavaScript at the Mozilla Web Docs. If you have experience in other languages,
you should be able to pick up JavaScript syntax quite quickly by reading the handbook.
The Handbook
The TypeScript Handbook is intended to be a comprehensive document that explains TypeScript to every-
day programmers. You can read the handbook by going from top to bottom in the left-hand navigation.
You should expect each chapter or page to provide you with a strong understanding of the given con-
cepts. The TypeScript Handbook is not a complete language specification, but it is intended to be a com-
prehensive guide to all of the language's features and behaviors.
In the interests of clarity and brevity, the main content of the Handbook will not explore every edge case
or minutiae of the features being covered. You can find more details on particular concepts in the refer-
ence articles.
Reference Files
242
The TypeScript Handbook
The reference section below the handbook in the navigation is built to provide a richer understanding of
how a particular part of TypeScript works. You can read it top-to-bottom, but each section aims to pro-
vide a deeper explanation of a single concept - meaning there is no aim for continuity.
Non-Goals
The Handbook is also intended to be a concise document that can be comfortably read in a few hours.
Certain topics won't be covered in order to keep things short.
Specifically, the Handbook does not fully introduce core JavaScript basics like functions, classes, and clo-
sures. Where appropriate, we'll include links to background reading that you can use to read up on those
concepts.
The Handbook also isn't intended to be a replacement for a language specification. In some cases, edge
cases or formal descriptions of behavior will be skipped in favor of high-level, easier-to-understand explana-
tions. Instead, there are separate reference pages that more precisely and formally describe many aspects
of TypeScript's behavior. The reference pages are not intended for readers unfamiliar with TypeScript, so
they may use advanced terminology or reference topics you haven't read about yet.
Finally, the Handbook won't cover how TypeScript interacts with other tools, except where necessary. Topics
like how to configure TypeScript with webpack, rollup, parcel, react, babel, closure, lerna, rush, bazel, pre-
act, vue, angular, svelte, jquery, yarn, or npm are out of scope - you can find these resources elsewhere on
the web.
Get Started
Before getting started with The Basics, we recommend reading one of the following introductory pages.
These introductions are intended to highlight key similarities and differences between TypeScript and your
favored programming language, and clear up common misconceptions specific to those languages.
Go to TOC
243
typescript
Throughout the sections you've read so far, we've been demonstrating basic TypeScript concepts using the
built-in functions present in all JavaScript runtimes. However, almost all JavaScript today includes many li-
braries to accomplish common tasks. Having types for the parts of your application that aren't your code
will greatly improve your TypeScript experience. Where do these types come from?
// @errors: 2339
const k = Math.max(5, 6);
const j = Math.mix(7, 8);
How did TypeScript know that max was present but not mix , even though Math 's implementation wasn't
part of your code?
The answer is that there are declaration files describing these built-in objects. A declaration file provides a
way to declare the existence of some types or values without actually providing implementations for those
values.
.d.ts files
TypeScript has two main kinds of files. .ts files are implementation files that contain types and executable
code. These are the files that produce .js outputs, and are where you'd normally write your code.
.d.ts files are declaration files that contain only type information. These files don't produce .js outputs;
they are only used for typechecking. We'll learn more about how to write our own declaration files later.
TypeScript names these declaration files with the pattern lib.[something].d.ts . If you navigate into a
file with that name, you can know that you're dealing with some built-in part of the platform, not user code.
target setting
The methods, properties, and functions available to you actually vary based on the version of JavaScript
your code is running on. For example, the startsWith method of strings is available only starting with the
version of JavaScript referred as ECMAScript 6.
244
Type Declarations
Being aware of what version of JavaScript your code ultimately runs on is important because you don't want
to use APIs that are from a newer version than the platform you deploy to. This is one function of the tar‐
get compiler setting.
TypeScript helps with this problem by varying which lib files are included by default based on your tar‐
get setting. For example, if target is ES5 , you will see an error if trying to use the startsWith method,
because that method is only available in ES6 or later.
lib setting
The lib setting allows more fine-grained control of which built-in declaration files are considered available
in your program. See the documentation page on lib for more information.
External Definitions
For non-built-in APIs, there are a variety of ways you can get declaration files. How you do this depends on
exactly which library you're getting types for.
Bundled Types
If a library you're using is published as an npm package, it may include type declaration files as part of its
distribution already. You can read the project's documentation to find out, or simply try importing the pack-
age and see if TypeScript is able to automatically resolve the types for you.
If you're a package author considering bundling type definitions with your package, you can read our guide
on bundling type definitions.
DefinitelyTyped / @types
The DefinitelyTyped repository is a centralized repo storing declaration files for thousands of libraries. The
vast majority of commonly-used libraries have declaration files available on DefinitelyTyped.
Definitions on DefinitelyTyped are also automatically published to npm under the @types scope. The name
of the types package is always the same as the name of the underlying package itself. For example, if you
installed the react npm package, you can install its corresponding types by running
TypeScript automatically finds type definitions under node_modules/@types , so there's no other step need-
ed to get these types available in your program.
245
typescript
If you want to silence warnings about a particular module without writing a declaration file, you can also
quick declare the module as type any by putting an empty declaration for it in a .d.ts file in your project.
For example, if you wanted to use a module named some-untyped-module without having definitions for it,
you would write:
Go to TOC
246
Conditional Types
At the heart of most useful programs, we have to make decisions based on input. JavaScript programs are
no different, but given the fact that values can be easily introspected, those decisions are also based on the
types of the inputs. Conditional types help describe the relation between the types of inputs and outputs.
interface Animal {
live(): void;
}
interface Dog extends Animal {
woof(): void;
}
Conditional types take a form that looks a little like conditional expressions ( condition ? trueExpression
: falseExpression ) in JavaScript:
When the type on the left of the extends is assignable to the one on the right, then you'll get the type in
the first branch (the "true" branch); otherwise you'll get the type in the latter branch (the "false" branch).
From the examples above, conditional types might not immediately seem useful - we can tell ourselves
whether or not Dog extends Animal and pick number or string ! But the power of conditional types
comes from using them with generics.
interface IdLabel {
id: number /* some fields */;
}
interface NameLabel {
name: string /* other fields */;
}
These overloads for createLabel describe a single JavaScript function that makes a choice based on the
types of its inputs. Note a few things:
247
typescript
1. If a library has to make the same sort of choice over and over throughout its API, this becomes
cumbersome.
2. We have to create three overloads: one for each case when we're sure of the type (one for string and
one for number ), and one for the most general case (taking a string | number ). For every new type
createLabel can handle, the number of overloads grows exponentially.
interface IdLabel {
id: number /* some fields */;
}
interface NameLabel {
name: string /* other fields */;
}
// ---cut---
type NameOrId<T extends number | string> = T extends number
? IdLabel
: NameLabel;
We can then use that conditional type to simplify our overloads down to a single function with no overloads.
interface IdLabel {
id: number /* some fields */;
}
interface NameLabel {
name: string /* other fields */;
}
type NameOrId<T extends number | string> = T extends number
? IdLabel
: NameLabel;
// ---cut---
function createLabel<T extends number | string>(idOrName: T): NameOrId<T> {
throw "unimplemented";
}
let a = createLabel("typescript");
// ^?
let b = createLabel(2.8);
// ^?
// @errors: 2536
type MessageOf<T> = T["message"];
248
Conditional Types
In this example, TypeScript errors because T isn't known to have a property called message . We could
constrain T , and TypeScript would no longer complain:
interface Email {
message: string;
}
However, what if we wanted MessageOf to take any type, and default to something like never if a mes‐
sage property isn't available? We can do this by moving the constraint out and introducing a conditional
type:
interface Email {
message: string;
}
interface Dog {
bark(): void;
}
Within the true branch, TypeScript knows that T will have a message property.
As another example, we could also write a type called Flatten that flattens array types to their element
types, but leaves them alone otherwise:
When Flatten is given an array type, it uses an indexed access with number to fetch out string[] 's
element type. Otherwise, it just returns the type it was given.
249
typescript
Conditional types provide us with a way to infer from types we compare against in the true branch using the
infer keyword. For example, we could have inferred the element type in Flatten instead of fetching it
out "manually" with an indexed access type:
Here, we used the infer keyword to declaratively introduce a new generic type variable named Item in-
stead of specifying how to retrieve the element type of T within the true branch. This frees us from having
to think about how to dig through and probing apart the structure of the types we're interested in.
We can write some useful helper type aliases using the infer keyword. For example, for simple cases, we
can extract the return type out from function types:
When inferring from a type with multiple call signatures (such as the type of an overloaded function), infer-
ences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not
possible to perform overload resolution based on a list of argument types.
If we plug a union type into ToArray , then the conditional type will be applied to each member of that
union.
250
Conditional Types
type StrArrOrNumArr =
// ---cut---
string | number;
and maps over each member type of the union, to what is effectively:
type StrArrOrNumArr =
// ---cut---
string[] | number[];
Typically, distributivity is the desired behavior. To avoid that behavior, you can surround each side of the
extends keyword with square brackets.
Go to TOC
251
typescript
A major part of software engineering is building components that not only have well-defined and consistent
APIs, but are also reusable. Components that are capable of working on the data of today as well as the
data of tomorrow will give you the most flexible capabilities for building up large software systems.
In languages like C# and Java, one of the main tools in the toolbox for creating reusable components is
generics, that is, being able to create a component that can work over a variety of types rather than a sin-
gle one. This allows users to consume these components and use their own types.
Without generics, we would either have to give the identity function a specific type:
Or, we could describe the identity function using the any type:
While using any is certainly generic in that it will cause the function to accept any and all types for the type
of arg , we actually are losing the information about what that type was when the function returns. If we
passed in a number, the only information we have is that any type could be returned.
Instead, we need a way of capturing the type of the argument in such a way that we can also use it to de-
note what is being returned. Here, we will use a type variable, a special kind of variable that works on types
rather than values.
We've now added a type variable Type to the identity function. This Type allows us to capture the type
the user provides (e.g. number ), so that we can use that information later. Here, we use Type again as
the return type. On inspection, we can now see the same type is used for the argument and the return
type. This allows us to traffic that type information in one side of the function and out the other.
We say that this version of the identity function is generic, as it works over a range of types. Unlike us-
ing any , it's also just as precise (i.e., it doesn't lose any information) as the first identity function that
used numbers for the argument and return type.
Once we've written the generic identity function, we can call it in one of two ways. The first way is to pass
all of the arguments, including the type argument, to the function:
252
Generics
Here we explicitly set Type to be string as one of the arguments to the function call, denoted using the
<> around the arguments rather than () .
The second way is also perhaps the most common. Here we use type argument inference -- that is, we
want the compiler to set the value of Type for us automatically based on the type of the argument we pass
in:
Notice that we didn't have to explicitly pass the type in the angle brackets ( <> ); the compiler just looked at
the value "myString" , and set Type to its type. While type argument inference can be a helpful tool to
keep code shorter and more readable, you may need to explicitly pass in the type arguments as we did in
the previous example when the compiler fails to infer the type, as may happen in more complex examples.
What if we want to also log the length of the argument arg to the console with each call? We might be
tempted to write this:
// @errors: 2339
function loggingIdentity<Type>(arg: Type): Type {
console.log(arg.length);
return arg;
}
When we do, the compiler will give us an error that we're using the .length member of arg , but nowhere
have we said that arg has this member. Remember, we said earlier that these type variables stand in for
any and all types, so someone using this function could have passed in a number instead, which does not
have a .length member.
253
typescript
Let's say that we've actually intended this function to work on arrays of Type rather than Type directly.
Since we're working with arrays, the .length member should be available. We can describe this just like
we would create arrays of other types:
You can read the type of loggingIdentity as "the generic function loggingIdentity takes a type para-
meter Type , and an argument arg which is an array of Type s, and returns an array of Type s." If we
passed in an array of numbers, we'd get an array of numbers back out, as Type would bind to number .
This allows us to use our generic type variable Type as part of the types we're working with, rather than
the whole type, giving us greater flexibility.
You may already be familiar with this style of type from other languages. In the next section, we'll cover
how you can create your own generic types like Array<Type> .
Generic Types
In previous sections, we created generic identity functions that worked over a range of types. In this
section, we'll explore the type of the functions themselves and how to create generic interfaces.
The type of generic functions is just like those of non-generic functions, with the type parameters listed
first, similarly to function declarations:
We could also have used a different name for the generic type parameter in the type, so long as the number
of type variables and how the type variables are used line up.
We can also write the generic type as a call signature of an object literal type:
254
Generics
Which leads us to writing our first generic interface. Let's take the object literal from the previous example
and move it to an interface:
interface GenericIdentityFn {
<Type>(arg: Type): Type;
}
In a similar example, we may want to move the generic parameter to be a parameter of the whole
interface. This lets us see what type(s) we're generic over (e.g. Dictionary<string> rather than just
Dictionary ). This makes the type parameter visible to all the other members of the interface.
interface GenericIdentityFn<Type> {
(arg: Type): Type;
}
Notice that our example has changed to be something slightly different. Instead of describing a generic
function, we now have a non-generic function signature that is a part of a generic type. When we use
GenericIdentityFn , we now will also need to specify the corresponding type argument (here: number ),
effectively locking in what the underlying call signature will use. Understanding when to put the type para-
meter directly on the call signature and when to put it on the interface itself will be helpful in describing
what aspects of a type are generic.
In addition to generic interfaces, we can also create generic classes. Note that it is not possible to create
generic enums and namespaces.
Generic Classes
A generic class has a similar shape to a generic interface. Generic classes have a generic type parameter list
in angle brackets ( <> ) following the name of the class.
// @strict: false
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
255
typescript
This is a pretty literal use of the GenericNumber class, but you may have noticed that nothing is restricting
it to only use the number type. We could have instead used string or even more complex objects.
// @strict: false
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
// ---cut---
let stringNumeric = new GenericNumber<string>();
stringNumeric.zeroValue = "";
stringNumeric.add = function (x, y) {
return x + y;
};
console.log(stringNumeric.add(stringNumeric.zeroValue, "test"));
Just as with interface, putting the type parameter on the class itself lets us make sure all of the properties
of the class are working with the same type.
As we cover in our section on classes, a class has two sides to its type: the static side and the instance side.
Generic classes are only generic over their instance side rather than their static side, so when working with
classes, static members can not use the class's type parameter.
Generic Constraints
If you remember from an earlier example, you may sometimes want to write a generic function that works
on a set of types where you have some knowledge about what capabilities that set of types will have. In our
loggingIdentity example, we wanted to be able to access the .length property of arg , but the com-
piler could not prove that every type had a .length property, so it warns us that we can't make this
assumption.
// @errors: 2339
function loggingIdentity<Type>(arg: Type): Type {
console.log(arg.length);
return arg;
}
Instead of working with any and all types, we'd like to constrain this function to work with any and all types
that also have the .length property. As long as the type has this member, we'll allow it, but it's required
to have at least this member. To do so, we must list our requirement as a constraint on what Type can be.
To do so, we'll create an interface that describes our constraint. Here, we'll create an interface that has a
single .length property and then we'll use this interface and the extends keyword to denote our
constraint:
256
Generics
interface Lengthwise {
length: number;
}
Because the generic function is now constrained, it will no longer work over any and all types:
// @errors: 2345
interface Lengthwise {
length: number;
}
Instead, we need to pass in values whose type has all the required properties:
interface Lengthwise {
length: number;
}
// @errors: 2345
function getProperty<Type, Key extends keyof Type>(obj: Type, key: Key) {
return obj[key];
}
let x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a");
getProperty(x, "m");
257
typescript
A more advanced example uses the prototype property to infer and constrain relationships between the
constructor function and the instance side of class types.
// @strict: false
class BeeKeeper {
hasMask: boolean = true;
}
class ZooKeeper {
nametag: string = "Mikle";
}
class Animal {
numLegs: number = 4;
}
createInstance(Lion).keeper.nametag;
createInstance(Bee).keeper.hasMask;
Go to TOC
258
Indexed Access Types
We can use an indexed access type to look up a specific property on another type:
The indexing type is itself a type, so we can use unions, keyof , or other types entirely:
You'll even see an error if you try to index a property that doesn't exist:
// @errors: 2339
type Person = { age: number; name: string; alive: boolean };
// ---cut---
type I1 = Person["alve"];
Another example of indexing with an arbitrary type is using number to get the type of an array's elements.
We can combine this with typeof to conveniently capture the element type of an array literal:
const MyArray = [
{ name: "Alice", age: 15 },
{ name: "Bob", age: 23 },
{ name: "Eve", age: 38 },
];
You can only use types when indexing, meaning you can't use a const to make a variable reference:
However, you can use a type alias for a similar style of refactor:
259
typescript
Go to TOC
260
Keyof Type Operator
If the type has a string or number index signature, keyof will return those types instead:
Note that in this example, M is string | number -- this is because JavaScript object keys are always co-
erced to a string, so obj[0] is always the same as obj["0"] .
keyof types become especially useful when combined with mapped types, which we'll learn more about
later.
Go to TOC
261
typescript
When you don't want to repeat yourself, sometimes a type needs to be based on another type.
Mapped types build on the syntax for index signatures, which are used to declare the types of properties
which have not been declared ahead of time:
A mapped type is a generic type which uses a union of PropertyKey s (frequently created via a keyof ) to
iterate through keys to create a type:
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
In this example, OptionsFlags will take all the properties from the type Type and change their values to
be a boolean.
type OptionsFlags<Type> = {
[Property in keyof Type]: boolean;
};
// ---cut---
type FeatureFlags = {
darkMode: () => void;
newUserProfile: () => void;
};
Mapping Modifiers
There are two additional modifiers which can be applied during mapping: readonly and ? which affect
mutability and optionality respectively.
You can remove or add these modifiers by prefixing with - or + . If you don't add a prefix, then + is
assumed.
type LockedAccount = {
readonly id: string;
readonly name: string;
};
262
Mapped Types
type MaybeUser = {
id: string;
name?: string;
age?: number;
};
type MappedTypeWithNewProperties<Type> = {
[Properties in keyof Type as NewKeyType]: Type[Properties]
}
You can leverage features like template literal types to create new property names from prior ones:
type Getters<Type> = {
[Property in keyof Type as `get${Capitalize<string & Property>}`]: () =>
Type[Property]
};
interface Person {
name: string;
age: number;
location: string;
}
You can filter out keys by producing never via a conditional type:
interface Circle {
kind: "circle";
radius: number;
}
You can map over arbitrary unions, not just unions of string | number | symbol , but unions of any type:
263
typescript
Further Exploration
Mapped types work well with other features in this type manipulation section, for example here is a mapped
type using a conditional type which returns either a true or false depending on whether an object has
the property pii set to the literal true :
type ExtractPII<Type> = {
[Property in keyof Type]: Type[Property] extends { pii: true } ? true : false;
};
type DBFields = {
id: { format: "incrementing" };
name: { type: string; pii: true };
};
Go to TOC
264
Template Literal Types
Template literal types build on string literal types, and have the ability to expand into many strings via
unions.
They have the same syntax as template literal strings in JavaScript, but are used in type positions. When
used with concrete literal types, a template literal produces a new string literal type by concatenating the
contents.
When a union is used in the interpolated position, the type is the set of every possible string literal that
could be represented by each union member:
For each interpolated position in the template literal, the unions are cross multiplied:
We generally recommend that people use ahead-of-time generation for large string unions, but this is useful
in smaller cases.
Consider the case where a function ( makeWatchedObject ) adds a new function called on() to a passed
object. In JavaScript, its call might look like: makeWatchedObject(baseObject) . We can imagine the base
object as looking like:
// @noErrors
const passedObject = {
firstName: "Saoirse",
lastName: "Ronan",
age: 26,
};
The on function that will be added to the base object expects two arguments, an eventName (a string )
and a callBack (a function ).
265
typescript
Should be passed a value of the type associated with the name attributeInThePassedObject ; thus,
since firstName is typed as string , the callback for the firstNameChanged event expects a string
to be passed to it at call time. Similarly events associated with age should expect to be called with a
number argument
Should have void return type (for simplicity of demonstration)
The naive function signature of on() might thus be: on(eventName: string, callBack: (newValue:
any) => void) . However, in the preceding description, we identified important type constraints that we'd
like to document in our code. Template Literal types let us bring these constraints into our code.
// @noErrors
declare function makeWatchedObject(obj: any): any;
// ---cut---
const person = makeWatchedObject({
firstName: "Saoirse",
lastName: "Ronan",
age: 26,
});
Notice that on listens on the event "firstNameChanged" , not just "firstName" . Our naive specification
of on() could be made more robust if we were to ensure that the set of eligible event names was con-
strained by the union of attribute names in the watched object with "Changed" added at the end. While we
are comfortable with doing such a calculation in JavaScript i.e. Object.keys(passedObject).map(x =>
`${x}Changed`) , template literals inside the type system provide a similar approach to string
manipulation:
type PropEventSource<Type> = {
on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) =>
void): void;
};
With this, we can build something that errors when given the wrong property:
// @errors: 2345
type PropEventSource<Type> = {
on(eventName: `${string & keyof Type}Changed`, callback: (newValue: any) =>
void): void;
};
266
Template Literal Types
// Prevent easy human error (using the key instead of the event name)
person.on("firstName", () => {});
// It's typo-resistant
person.on("frstNameChanged", () => {});
The key insight that makes this possible is this: we can use a function with a generic such that:
type PropEventSource<Type> = {
on<Key extends string & keyof Type>
(eventName: `${Key}Changed`, callback: (newValue: Type[Key]) => void ):
void;
};
267
typescript
When a user calls with the string "firstNameChanged" , TypeScript will try to infer the right type for Key .
To do that, it will match Key against the content prior to "Changed" and infer the string "firstName" .
Once TypeScript figures that out, the on method can fetch the type of firstName on the original object,
which is string in this case. Similarly, when called with "ageChanged" , TypeScript finds the type for the
property age which is number .
Inference can be combined in different ways, often to deconstruct strings, and reconstruct them in different
ways.
Uppercase<StringType>
Converts each character in the string to the uppercase version.
Example
Lowercase<StringType>
Converts each character in the string to the lowercase equivalent.
Example
Capitalize<StringType>
Converts the first character in the string to an uppercase equivalent.
268
Template Literal Types
Example
Uncapitalize<StringType>
Converts the first character in the string to a lowercase equivalent.
Example
Go to TOC
269
typescript
// Prints "string"
console.log(typeof "Hello world");
TypeScript adds a typeof operator you can use in a type context to refer to the type of a variable or
property:
let s = "hello";
let n: typeof s;
// ^?
This isn't very useful for basic types, but combined with other type operators, you can use typeof to con-
veniently express many patterns. For an example, let's start by looking at the predefined type
ReturnType<T> . It takes a function type and produces its return type:
// @errors: 2749
function f() {
return { x: 10, y: 3 };
}
type P = ReturnType<f>;
Remember that values and types aren't the same thing. To refer to the type that the value f has, we use
typeof :
function f() {
return { x: 10, y: 3 };
}
type P = ReturnType<typeof f>;
// ^?
Limitations
TypeScript intentionally limits the sorts of expressions you can use typeof on.
Specifically, it's only legal to use typeof on identifiers (i.e. variable names) or their properties. This helps
avoid the confusing trap of writing code you think is executing, but isn't:
// @errors: 1005
declare const msgbox: () => boolean;
// type msgbox = any;
// ---cut---
// Meant to use = ReturnType<typeof msgbox>
let shouldContinue: typeof msgbox("Are you sure you want to continue?");
270
Creating Types from Types
TypeScript's type system is very powerful because it allows expressing types in terms of other types.
The simplest form of this idea is generics, we actually have a wide variety of type operators available to
use. It's also possible to express types in terms of values that we already have.
By combining various type operators, we can express complex operations and values in a succinct, main-
tainable way. In this section we'll cover ways to express a new type in terms of an existing type or value.
Go to TOC
271
typescript
Understanding Errors
Whenever TypeScript finds an error, it tries to explain what went wrong in as much detail as possible.
Because its type system is structural, this often means providing somewhat lengthy descriptions of where it
found a problem.
Terminology
There is some terminology you'll frequently see in error messages that is helpful to understand.
assignable to
TypeScript considers a type assignable to another type if one is an acceptable substitute for the other. In
other words, a Cat is assignable to an Animal because a Cat is an acceptable substitute for an Animal .
As its name implies, this relationship is used to check the validity of an assignment t = s; by examining
the types of t and s . It's also used to check most other places where two types interact. For example,
when calling a function, each argument's type must be assignable to parameter's declared type.
Informally, if you see T is not assignable to S , you can think of that as TypeScript saying " T and S
are not compatible". However, note that this is a directional relationship: S being assignable to T does not
imply that T is assignable to S .
Examples
Let's look at some example error messages and understand what's going on.
Error Elaborations
Each error starts with a leading message, sometimes followed by more sub-messages. You can think of
each sub-message as answering a "why?" question about the message above it. Let's work through some
examples to see how they work in practice.
Here's an example that produces an error message longer than the example itself:
// @errors: 2322
let a: { m: number[] };
let b = { m: [""] };
a = b;
TypeScript found an error when checking the last line. Its logic for issuing an error follows from its logic for
determining if the assignment is OK:
272
Understanding Errors
Extra Properties
// @errors: 2322
type A = { m: number };
const a: A = { m: 10, n: "" };
Union Assignments
// @errors: 2322
type Thing = "none" | { name: string };
Go to TOC
273
typescript
With TypeScript 3.7, TypeScript added support for generating .d.ts files from JavaScript using JSDoc syntax.
This set up means you can own the editor experience of TypeScript-powered editors without porting your
project to TypeScript, or having to maintain .d.ts files in your codebase. TypeScript supports most JSDoc
tags, you can find the reference here.
Adding TypeScript
You can learn how to do this in our installation page.
TSConfig
The TSConfig is a jsonc file which configures both your compiler flags, and declare where to find files. In
this case, you will want a file like the following:
{
// Change this to match your project
"include": ["src/**/*"],
"compilerOptions": {
// Tells TypeScript to read JS files, as
// normally they are ignored as source files
"allowJs": true,
// Generate d.ts files
"declaration": true,
// This compiler run should
// only output d.ts files
"emitDeclarationOnly": true,
// Types should go into this directory.
// Removing this would place the .d.ts files
// next to the .js files
"outDir": "dist",
// go to js file when using IDE functions like
// "Go to Definition" in VSCode
"declarationMap": true
}
}
You can learn more about the options in the tsconfig reference. An alternative to using a TSConfig file is the
CLI, this is the same behavior as a CLI command.
274
Creating .d.ts Files from .js files
"main":"index.js" index.d.ts
"main":"./dist/index.js" ./dist/index.d.ts
Tips
If you'd like to write tests for your .d.ts files, try tsd.
Go to TOC
275
typescript
The type system in TypeScript has different levels of strictness when working with a codebase:
Each step represents a move towards a safer type-system, but not every project needs that level of
verification.
JSDoc annotations come before a declaration will be used to set the type of that declaration. For example:
x = 0; // OK
x = false; // OK?!
You can find the full list of supported JSDoc patterns in JSDoc Supported Types.
@ts-check
The last line of the previous code sample would raise an error in TypeScript, but it doesn't by default in a JS
project. To enable errors in your JavaScript files add: // @ts-check to the first line in your .js files to
have TypeScript raise it as an error.
// @ts-check
// @errors: 2322
/** @type {number} */
var x;
x = 0; // OK
x = false; // Not OK
If you have a lot of JavaScript files you want to add errors to then you can switch to using a jsconfig.j‐
son . You can skip checking some files by adding a // @ts-nocheck comment to files.
276
JS Projects Utilizing TypeScript
TypeScript may offer you errors which you disagree with, in those cases you can ignore errors on specific
lines by adding // @ts-ignore or // @ts-expect-error on the preceding line.
// @ts-check
/** @type {number} */
var x;
x = 0; // OK
// @ts-expect-error
x = false; // Not OK
To learn more about how JavaScript is interpreted by TypeScript read How TS Type Checks JS
Go to TOC
277
typescript
The list below outlines which constructs are currently supported when using JSDoc annotations to provide
type information in JavaScript files.
Note any tags which are not explicitly listed below (such as @async ) are not yet supported.
Types
@type
@param (or @arg or @argument )
@returns (or @return )
@typedef
@callback
@template
Classes
Documentation
@deprecated
@see
@link
Other
@enum
@author
Other supported patterns
Unsupported patterns
Unsupported tags
The meaning is usually the same, or a superset, of the meaning of the tag given at jsdoc.app. The code be-
low describes the differences and gives some example usage of each tag.
278
JSDoc Reference
Types
@type
You can reference types with the "@type" tag. The type can be:
You can use most JSDoc type syntax and any TypeScript syntax, from the most basic like string to the
most advanced, like conditional types.
/**
* @type {string}
*/
var s;
@type can specify a union type — for example, something can be either a string or a boolean.
/**
* @type {string | boolean}
*/
var sb;
You can also specify object literal types. For example, an object with properties 'a' (string) and 'b' (number)
uses the following syntax:
You can specify map-like and array-like objects using string and number index signatures, using either stan-
dard JSDoc syntax or TypeScript syntax.
279
typescript
/**
* A map-like object that maps arbitrary `string` properties to `number`s.
*
* @type {Object.<string, number>}
*/
var stringToNumber;
The preceding two types are equivalent to the TypeScript types { [x: string]: number } and { [x:
number]: any } . The compiler understands both syntaxes.
You can specify function types using either TypeScript or Google Closure syntax:
/**
* @type {*} - can be 'any' type
*/
var star;
/**
* @type {?} - unknown type (same as 'any')
*/
var question;
Casts
TypeScript borrows cast syntax from Google Closure. This lets you cast types to other types by adding a
@type tag before any parenthesized expression.
/**
* @type {number | string}
*/
var numberOrString = Math.random() < 0.5 ? "hello" : 100;
var typeAssertedNumber = /** @type {number} */ (numberOrString);
280
JSDoc Reference
Import types
You can import declarations from other files using import types. This syntax is TypeScript-specific and dif-
fers from the JSDoc standard:
// @filename: types.d.ts
export type Pet = {
name: string,
};
// @filename: main.js
/**
* @param { import("./types").Pet } p
*/
function walk(p) {
console.log(`Walking ${p.name}...`);
}
// @filename: types.d.ts
export type Pet = {
name: string,
};
// @filename: main.js
// ---cut---
/**
* @typedef { import("./types").Pet } Pet
*/
/**
* @type {Pet}
*/
var myPet;
myPet.name;
import types can be used to get the type of a value from a module if you don't know the type, or if it has a
large type that is annoying to type:
// @filename: accounts.d.ts
export const userAccount = {
name: "Name",
address: "An address",
postalCode: "",
country: "",
planet: "",
system: "",
galaxy: "",
universe: "",
};
// @filename: main.js
// ---cut---
/**
* @type {typeof import("./accounts").userAccount }
*/
var x = require("./accounts").userAccount;
281
typescript
/**
* @return {PromiseLike<string>}
*/
function ps() {}
/**
* @returns {{ a: string, b: number }} - May use '@returns' as well as '@return'
*/
function ab() {}
/**
* @typedef {Object} SpecialType - creates a new type named 'SpecialType'
* @property {string} prop1 - a string property of SpecialType
* @property {number} prop2 - a number property of SpecialType
* @property {number=} prop3 - an optional number property of SpecialType
* @prop {number} [prop4] - an optional number property of SpecialType
* @prop {number} [prop5=42] - an optional number property of SpecialType with
default
*/
/**
* @typedef {object} SpecialType1 - creates a new type named 'SpecialType'
* @property {string} prop1 - a string property of SpecialType
* @property {number} prop2 - a number property of SpecialType
* @property {number=} prop3 - an optional number property of SpecialType
*/
282
JSDoc Reference
@param allows a similar syntax for one-off type specifications. Note that the nested property names must
be prefixed with the name of the parameter:
/**
* @param {Object} options - The shape is the same as SpecialType above
* @param {string} options.prop1
* @param {number} options.prop2
* @param {number=} options.prop3
* @param {number} [options.prop4]
* @param {number} [options.prop5=42]
*/
function special(options) {
return (options.prop4 || 1001) + options.prop5;
}
@callback is similar to @typedef , but it specifies a function type instead of an object type:
/**
* @callback Predicate
* @param {string} data
* @param {number} [index]
* @returns {boolean}
*/
Of course, any of these types can be declared using TypeScript syntax in a single-line @typedef :
@template
You can declare type parameters with the @template tag. This lets you make functions, classes, or types
that are generic:
/**
* @template T
* @param {T} x - A generic parameter that flows through to the return type
* @returns {T}
*/
function id(x) {
return x;
}
const a = id("string");
const b = id(123);
const c = id({});
283
typescript
/**
* @template T,U,V
* @template W,X
*/
You can also specify a type constraint before the type parameter name. Only the first type parameter in a
list is constrained:
/**
* @template {string} K - K must be a string or string literal
* @template {{ serious(): string }} Seriousalizable - must have a serious method
* @param {K} key
* @param {Seriousalizable} object
*/
function seriousalize(key, object) {
// ????
}
Classes
Classes can be declared as ES6 classes.
class C {
/**
* @param {number} data
*/
constructor(data) {
// property types can be inferred
this.name = "foo";
// or set explicitly
/** @type {string | null} */
this.title = null;
284
JSDoc Reference
They can also be declared as constructor functions; use @constructor along with @this for this.
Property Modifiers
@public , @private , and @protected work exactly like public , private , and protected in
TypeScript:
// @errors: 2341
// @ts-check
class Car {
constructor() {
/** @private */
this.identifier = 100;
}
printIdentifier() {
console.log(this.identifier);
}
}
@public is always implied and can be left off, but means that a property can be reached from
anywhere.
@private means that a property can only be used within the containing class.
@protected means that a property can only be used within the containing class, and all derived sub-
classes, but not on dissimilar instances of the containing class.
@readonly
The @readonly modifier ensures that a property is only ever written to during initialization.
// @errors: 2540
// @ts-check
class Car {
constructor() {
/** @readonly */
this.identifier = 100;
}
printIdentifier() {
console.log(this.identifier);
}
}
285
typescript
@override
@override works the same way as in TypeScript; use it on methods that override a method from a base
class:
export class C {
m() { }
}
class D extends C {
/** @override */
m() { }
}
@extends
When JavaScript classes extend a generic base class, there is no JavaScript syntax for passing a type argu-
ment. The @extends tag allows this:
/**
* @template T
* @extends {Set<T>}
*/
class SortableSet extends Set {
// ...
}
Note that @extends only works with classes. Currently, there is no way for a constructor function to extend
a class.
@implements
In the same way, there is no JavaScript syntax for implementing a TypeScript interface. The @implements
tag works just like in TypeScript:
@constructor
The compiler infers constructor functions based on this-property assignments, but you can make checking
stricter and suggestions better if you add a @constructor tag:
// @checkJs
// @errors: 2345 2348
/**
286
JSDoc Reference
* @constructor
* @param {number} data
*/
function C(data) {
// property types can be inferred
this.name = "foo";
// or set explicitly
/** @type {string | null} */
this.title = null;
this.initialize(data);
}
/**
* @param {string} s
*/
C.prototype.initialize = function (s) {
this.size = s.length;
};
Note: Error messages only show up in JS codebases with a JSConfig and checkJs enabled.
With @constructor , this is checked inside the constructor function C , so you will get suggestions for
the initialize method and an error if you pass it a number. Your editor may also show warnings if you
call C instead of constructing it.
Unfortunately, this means that constructor functions that are also callable cannot use @constructor .
@this
The compiler can usually figure out the type of this when it has some context to work with. When it
doesn't, you can explicitly specify the type of this with @this :
/**
* @this {HTMLElement}
* @param {*} e
*/
function callbackForLater(e) {
this.clientHeight = parseInt(e); // should be fine!
}
Documentation
@deprecated
287
typescript
When a function, method, or property is deprecated you can let users know by marking it with a /** @dep‐
recated */ JSDoc comment. That information is surfaced in completion lists and as a suggestion diagnos-
tic that editors can handle specially. In an editor like VS Code, deprecated values are typically displayed in a
strike-through style like this.
// @noErrors
/** @deprecated */
const apiV1 = {};
const apiV2 = {};
apiV;
// ^|
@see
@see lets you link to other names in your program:
type Box<T> = { t: T }
/** @see Box for implementation details */
type Boxify<T> = { [K in keyof T]: Box<T> };
Some editors will turn Box into a link to make it easy to jump there and back.
@link
@link is like @see , except that it can be used inside other tags:
type Box<T> = { t: T }
/** @returns A {@link Box} containing the parameter. */
function box<U>(u: U): Box<U> {
return { t: u };
}
Other
@enum
The @enum tag allows you to create an object literal whose members are all of a specified type. Unlike most
object literals in JavaScript, it does not allow other members. @enum is intended for compatibility with
Google Closure's @enum tag.
JSDocState.SawAsterisk;
Note that @enum is quite different from, and much simpler than, TypeScript's enum . However, unlike
TypeScript's enums, @enum can have any type:
288
JSDoc Reference
MathFuncs.add1;
@author
You can specify the author of an item with @author :
/**
* Welcome to awesome.ts
* @author Ian Awesome <[email protected]>
*/
Remember to surround the email address with angle brackets. Otherwise, @example will be parsed as a
new tag.
class Foo {}
// ---cut---
var someObj = {
/**
* @param {string} param1 - JSDocs on property assignments work
*/
x: function (param1) {},
};
/**
* As do jsdocs on variable assignments
* @return {Window}
*/
let someFunc = function () {};
/**
* And class methods
* @param {string} greeting The greeting to use
*/
Foo.prototype.sayHi = (greeting) => console.log("Hi!");
/**
* And arrow function expressions
* @param {number} x - A multiplier
*/
let myArrow = (x) => x * x;
/**
* Which means it works for function components in JSX too
* @param {{a: string, b: number}} props - Some param
*/
var fc = (props) => <div>{props.a.charAt(0)}</div>;
/**
* A parameter can be a class constructor, using Google Closure syntax.
*
289
typescript
/**
* @param {...string} p1 - A 'rest' arg (array) of strings. (treated as 'any')
*/
function fn10(p1) {}
/**
* @param {...string} p1 - A 'rest' arg (array) of strings. (treated as 'any')
*/
function fn9(p1) {
return p1.join();
}
Unsupported patterns
Postfix equals on a property type in an object literal type doesn't specify an optional property:
/**
* @type {{ a: string, b: number= }}
*/
var wrong;
/**
* Use postfix question on the property name instead:
* @type {{ a: string, b?: number }}
*/
var right;
/**
* @type {?number}
* With strictNullChecks: true -- number | null
* With strictNullChecks: false -- number
*/
var nullable;
/**
* @type {number | null}
* With strictNullChecks: true -- number | null
* With strictNullChecks: false -- number
*/
var unionNullable;
Non-nullable types have no meaning and are treated just as their original type:
/**
* @type {!number}
* Just has type number
*/
var normal;
290
JSDoc Reference
Unlike JSDoc's type system, TypeScript only allows you to mark types as containing null or not. There is no
explicit non-nullability -- if strictNullChecks is on, then number is not nullable. If it is off, then number is
nullable.
Unsupported tags
TypeScript ignores any unsupported JSDoc tags.
Go to TOC
291
typescript
Here are some notable differences on how checking works in .js files compared to .ts files.
In a .js file, the compiler infers properties from property assignments inside the class body. The type of a
property is the type given in the constructor, unless it's not defined there, or the type in the constructor is
undefined or null. In that case, the type is the union of the types of all the right-hand values in these as-
signments. Properties defined in the constructor are always assumed to exist, whereas ones defined just in
methods, getters, or setters are considered optional.
// @checkJs
// @errors: 2322
class C {
constructor() {
this.constructorOnly = 0;
this.constructorUnknown = undefined;
}
method() {
this.constructorOnly = false;
this.constructorUnknown = "plunkbat"; // ok, constructorUnknown is string |
undefined
this.methodOnly = "ok"; // ok, but methodOnly could also be undefined
}
method2() {
this.methodOnly = true; // also, ok, methodOnly's type is string | boolean |
undefined
}
}
If properties are never set in the class body, they are considered unknown. If your class has properties that
are only read from, add and then annotate a declaration in the constructor with JSDoc to specify the type.
You don't even have to give a value if it will be initialized later:
// @checkJs
// @errors: 2322
class C {
constructor() {
/** @type {number | undefined} */
this.prop = undefined;
/** @type {number | undefined} */
this.count;
}
}
292
Type Checking JavaScript Files
// @checkJs
// @errors: 2683 2322
function C() {
this.constructorOnly = 0;
this.constructorUnknown = undefined;
}
C.prototype.method = function () {
this.constructorOnly = false;
this.constructorUnknown = "plunkbat"; // OK, the type is string | undefined
};
The module support in JavaScript is much more syntactically forgiving than TypeScript's module support.
Most combinations of assignments and declarations are supported.
class C {}
C.D = class {};
function Outer() {
this.y = 2;
}
Outer.Inner = function () {
this.yy = 2;
};
Outer.Inner();
293
typescript
var ns = {};
ns.C = class {};
ns.func = function () {};
ns;
// IIFE
var ns = (function (n) {
return n || {};
})();
ns.CONST = 1;
// defaulting to global
var assign =
assign ||
function () {
// code goes here
};
assign.extra = 1;
var obj = { a: 1 };
obj.b = 2; // Allowed
Object literals behave as if they have an index signature [x:string]: any that allows them to be treated
as open maps instead of closed objects.
Like other special JS checking behaviors, this behavior can be changed by specifying a JSDoc type for the
variable. For example:
// @checkJs
// @errors: 2339
/** @type {{a: number}} */
var obj = { a: 1 };
obj.b = 2;
294
Type Checking JavaScript Files
It is important to note that it is an error to call a function with too many arguments.
For instance:
// @checkJs
// @strict: false
// @errors: 7006 7006 2554
function bar(a, b) {
console.log(a + " " + b);
}
JSDoc annotated functions are excluded from this rule. Use JSDoc optional parameter syntax ( [ ] ) to ex-
press optionality. e.g.:
/**
* @param {string} [somebody] - Somebody's name.
*/
function sayHello(somebody) {
if (!somebody) {
somebody = "John Doe";
}
console.log("Hello " + somebody);
}
sayHello();
295
typescript
In extends clause
For instance, React.Component is defined to have two type parameters, Props and State . In a .js file,
there is no legal way to specify these in the extends clause. By default the type arguments will be any :
/**
* @augments {Component<{a: number}, State>}
*/
class MyComponent extends Component {
render() {
this.props.b; // Error: b does not exist on {a:number}
}
}
In JSDoc references
An unspecified type argument in JSDoc defaults to any:
296
Type Checking JavaScript Files
/** @type{Array} */
var x = [];
x.push(1); // OK
x.push("string"); // OK, x is of type Array<any>
/** @type{Array.<number>} */
var y = [];
y.push(1); // OK
y.push("string"); // Error, string is not assignable to number
In function calls
A call to a generic function uses the arguments to infer the type parameters. Sometimes this process fails to
infer any types, mainly because of lack of inference sources; in these cases, the type parameters will de-
fault to any . For example:
p; // Promise<any>;
Go to TOC
297
typescript
# Emit JS for any .ts files in the folder src, with the default settings
tsc src/*.ts
# Emit d.ts files for a js file with showing compiler options which are booleans
tsc index.js --declaration --emitDeclarationOnly
# Emit a single .js file from two files via compiler options which take string
arguments
tsc app.ts util.ts --target esnext --outfile index.js
Compiler Options
If you're looking for more information about the compiler options in a tsconfig, check out the
TSConfig Reference
CLI Commands
Flag Type
--all boolean
--generateTrace string
--help boolean
298
tsc CLI Options
Flag Type
--init boolean
--listFilesOnly boolean
Print names of files that are part of the compilation and then stop processing.
--locale string
Set the language of the messaging from TypeScript. This does not affect emit.
--project string
Compile the project given the path to its configuration file, or to a folder with a 'tsconfig.json'.
--showConfig boolean
--version boolean
Build Options
Flag Type
--build boolean
--clean boolean
299
typescript
Flag Type
--dry boolean
--force boolean
--verbose boolean
Watch Options
Flag Type
--
list
excludeDirectories
--excludeFiles list
Specify what approach the watcher should use if the system runs out of native file watchers.
--
synchronousWatchDi‐ boolean
rectory
300
tsc CLI Options
Flag Type
Synchronously call callbacks and update the state of directory watchers on platforms that don`t support
recursive watching natively.
--watch boolean
Specify how directories are watched on systems that lack recursive file-watching functionality.
Compiler Flags
false otherwise.
Allow 'import x from y' when a module doesn't have a default export.
301
typescript
--allowUnreachableCode boolean
--allowUnusedLabels boolean
true if strict ,
--alwaysStrict boolean
false otherwise.
--
assumeChangesOnlyAffectDirectDepen‐ boolean false
dencies
--baseUrl string
302
tsc CLI Options
true if composite ,
--declaration boolean
false otherwise.
Generate .d.ts files from TypeScript and JavaScript files in your project.
--declarationDir string
Remove the 20mb cap on total source code size for JavaScript files in
the TypeScript language server.
303
typescript
--
disableSourceOfProjectReferenceRedi‐ boolean false
rect
Emit more compliant, but verbose and less performant JavaScript for
iteration.
Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files.
304
tsc CLI Options
Print files read during the compilation including why it was included.
Allow importing helper functions from tslib once per project, instead of
including them per-file.
remove , pre‐
--importsNotUsedAsValues serve , or er‐ remove
ror
Specify emit/checking behavior for imports that are only used for types.
305
typescript
true if composite ,
--incremental boolean
false otherwise.
Ensure that each file can be safely transpiled without relying on other
imports.
preserve , re‐
act , react-
--jsx native ,
react-jsx , or
react-jsxdev
Specify the JSX factory function used when targeting React JSX emit,
e.g. 'React.createElement' or 'h'.
306
tsc CLI Options
Specify the JSX Fragment reference used for fragments when targeting
React JSX emit e.g. 'React.Fragment' or 'Fragment'.
Specify module specifier used to import the JSX factory functions when
using jsx: react-jsx* .
--lib list
Specify a set of bundled library declaration files that describe the target
runtime environment.
--mapRoot string
Specify the location where debugger should locate map files instead of
generated locations.
--maxNodeModuleJsDepth number 0
307
typescript
Specify the maximum folder depth used for checking JavaScript files
from node_modules . Only applicable with allowJs .
none , common‐
js , amd , umd ,
system , CommonJS if target is
es6 / es2015 , ES3 or ES5 ,
--module
es2020 , es‐
2022 , esnext , ES6 / ES2015 otherwise.
node16 , or
nodenext
Classic if module is
AMD , UMD , System or
classic , ES6 / ES2015 ,
--moduleResolution node , node16 ,
Matches if module is
or nodenext
node12 or nodenext ,
Node otherwise.
--moduleSuffixes list
308
tsc CLI Options
true if strict ,
--noImplicitAny boolean
false otherwise.
309
typescript
true if strict ,
--noImplicitThis boolean
false otherwise.
--
boolean false
noPropertyAccessFromIndexSignature
310
tsc CLI Options
--out string
--outDir string
--outFile string
Specify a file that bundles all outputs into one JavaScript file. If decla‐
ration is true, also designates a file that bundles all .d.ts output.
--paths object
--plugins list
311
typescript
true if isolatedMod‐
ules ,
--preserveConstEnums boolean
false otherwise.
Specify the object invoked for createElement . This only applies when
targeting react JSX emit.
312
tsc CLI Options
Skip type checking .d.ts files that are included with TypeScript.
--sourceRoot string
Specify the root path for debuggers to find the reference source code.
313
typescript
true if strict ,
--strictBindCallApply boolean
false otherwise.
Check that the arguments for bind , call , and apply methods
match the original function.
true if strict ,
--strictFunctionTypes boolean
false otherwise.
true if strict ,
--strictNullChecks boolean
false otherwise.
true if strict ,
--strictPropertyInitialization boolean
false otherwise.
Check for class properties that are declared but not set in the
constructor.
314
tsc CLI Options
es3 , es5 ,
es6 / es2015 ,
es2016 , es‐
2017 , es2018 ,
--target ES3
es2019 , es‐
2020 , es2021 ,
es2022 , or es‐
next
Set the JavaScript language version for emitted JavaScript and include
compatible library declarations.
--typeRoots list
--
list
types
315
typescript
true if target is
ES2022 or higher, includ-
--useDefineForClassFields boolean ing ESNext ,
false otherwise.
true if strict ,
--useUnknownInCatchVariables boolean
false otherwise.
Related
Every option is fully explained in the TSConfig Reference.
Learn how to use a tsconfig.json files.
Learn how to work in an MSBuild project.
Go to TOC
316
Compiler Options in MSBuild
Overview
When you have an MSBuild based project which utilizes TypeScript such as an ASP.NET Core project, you
can configure TypeScript in two ways. Either via a tsconfig.json or via the project settings.
Using a tsconfig.json
We recommend using a tsconfig.json for your project when possible. To add one to an existing project,
add a new item to your project which is called a "TypeScript JSON Configuration File" in modern versions of
Visual Studio.
The new tsconfig.json will then be used as the source of truth for TypeScript-specific build information
like files and configuration. You can learn about how TSConfigs works here and there is a comprehensive
reference here.
<PropertyGroup>
<TypeScriptNoEmitOnError>true</TypeScriptNoEmitOnError>
<TypeScriptNoImplicitReturns>true</TypeScriptNoImplicitReturns>
</PropertyGroup>
There is a series of mappings for common TypeScript settings, these are settings which map directly to
TypeScript cli options and are used to help you write a more understandable project file. You can use the
TSConfig reference to get more information on what values and defaults are for each mapping.
CLI Mappings
MSBuild
Config TSC Flag
Name
<TypeScriptAllowJS> --allowJs
Allow JavaScript files to be a part of your program. Use the checkJS option to get errors from these
files.
<TypeScriptRemoveComments> --removeComments
<TypeScriptNoImplicitAny> --noImplicitAny
317
typescript
MSBuild
Config TSC Flag
Name
Enable error reporting for expressions and declarations with an implied any type..
<TypeScriptGeneratesDeclarations> --declaration
Generate .d.ts files from TypeScript and JavaScript files in your project.
<TypeScriptModuleKind> --module
<TypeScriptJSXEmit> --jsx
<TypeScriptOutDir> --outDir
<TypeScriptSourceMap> --sourcemap
<TypeScriptTarget> --target
Set the JavaScript language version for emitted JavaScript and include compatible library declarations.
<TypeScriptNoResolve> --noResolve
Disallow import s, require s or <reference> s from expanding the number of files TypeScript should
add to a project.
<TypeScriptMapRoot> --mapRoot
Specify the location where debugger should locate map files instead of generated locations.
<TypeScriptSourceRoot> --sourceRoot
Specify the root path for debuggers to find the reference source code.
<TypeScriptCharset> --charset
318
Compiler Options in MSBuild
MSBuild
Config TSC Flag
Name
No longer supported. In early versions, manually set the text encoding for reading files.
<TypeScriptEmitBOM> --emitBOM
Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files.
<TypeScriptNoLib> --noLib
<TypeScriptPreserveConstEnums> --preserveConstEnums
--
<TypeScriptSuppressImplicitAnyIndexErrors> suppressImplicitAnyIndexEr‐
rors
Suppress noImplicitAny errors when indexing objects that lack index signatures.
<TypeScriptNoEmitHelpers> --noEmitHelpers
<TypeScriptInlineSourceMap> --inlineSourceMap
<TypeScriptInlineSources> --inlineSources
<TypeScriptNewLine> --newLine
<TypeScriptIsolatedModules> --isolatedModules
Ensure that each file can be safely transpiled without relying on other imports.
<TypeScriptEmitDecoratorMetadata> --emitDecoratorMetadata
319
typescript
MSBuild
Config TSC Flag
Name
<TypeScriptRootDir> --rootDir
<TypeScriptExperimentalDecorators> --experimentalDecorators
<TypeScriptModuleResolution> --moduleResolution
--
<TypeScriptSuppressExcessPropertyErrors>
suppressExcessPropertyErrors
Disable reporting of excess property errors during the creation of object literals.
<TypeScriptReactNamespace> --reactNamespace
Specify the object invoked for createElement . This only applies when targeting react JSX emit.
<TypeScriptSkipDefaultLibCheck> --skipDefaultLibCheck
Skip type checking .d.ts files that are included with TypeScript.
<TypeScriptAllowUnusedLabels> --allowUnusedLabels
<TypeScriptNoImplicitReturns> --noImplicitReturns
Enable error reporting for codepaths that do not explicitly return in a function.
<TypeScriptNoFallthroughCasesInSwitch> --noFallthroughCasesInSwitch
<TypeScriptAllowUnreachableCode> --allowUnreachableCode
320
Compiler Options in MSBuild
MSBuild
Config TSC Flag
Name
--
<TypeScriptForceConsistentCasingInFile‐
forceConsistentCasingInFile‐
Names>
Names
--
<TypeScriptAllowSyntheticDefaultImports>
allowSyntheticDefaultImports
Allow 'import x from y' when a module doesn't have a default export.
<TypeScriptNoImplicitUseStrict> --noImplicitUseStrict
<TypeScriptLib> --lib
Specify a set of bundled library declaration files that describe the target runtime environment.
<TypeScriptBaseUrl> --baseUrl
<TypeScriptDeclarationDir> --declarationDir
<TypeScriptNoImplicitThis> --noImplicitThis
<TypeScriptSkipLibCheck> --skipLibCheck
<TypeScriptStrictNullChecks> --strictNullChecks
321
typescript
MSBuild
Config TSC Flag
Name
<TypeScriptNoUnusedLocals> --noUnusedLocals
<TypeScriptNoUnusedParameters> --noUnusedParameters
<TypeScriptAlwaysStrict> --alwaysStrict
<TypeScriptImportHelpers> --importHelpers
Allow importing helper functions from tslib once per project, instead of including them per-file.
<TypeScriptJSXFactory> --jsxFactory
Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'
<TypeScriptStripInternal> --stripInternal
<TypeScriptCheckJs> --checkJs
<TypeScriptDownlevelIteration> --downlevelIteration
Emit more compliant, but verbose and less performant JavaScript for iteration.
<TypeScriptStrict> --strict
<TypeScriptNoStrictGenericChecks> --noStrictGenericChecks
<TypeScriptPreserveSymlinks> --preserveSymlinks
322
Compiler Options in MSBuild
MSBuild
Config TSC Flag
Name
Disable resolving symlinks to their realpath. This correlates to the same flag in node.
<TypeScriptStrictFunctionTypes> --strictFunctionTypes
When assigning functions, check to ensure parameters and the return values are subtype-compatible.
--
<TypeScriptStrictPropertyInitialization>
strictPropertyInitialization
Check for class properties that are declared but not set in the constructor.
<TypeScriptESModuleInterop> --esModuleInterop
Emit additional JavaScript to ease support for importing CommonJS modules. This enables allowSyn‐
theticDefaultImports for type compatibility.
<TypeScriptEmitDeclarationOnly> --emitDeclarationOnly
<TypeScriptKeyofStringsOnly> --keyofStringsOnly
Make keyof only return strings instead of string, numbers or symbols. Legacy option.
<TypeScriptUseDefineForClassFields> --useDefineForClassFields
<TypeScriptDeclarationMap> --declarationMap
<TypeScriptResolveJsonModule> --resolveJsonModule
<TypeScriptStrictBindCallApply> --strictBindCallApply
Check that the arguments for bind , call , and apply methods match the original function.
<TypeScriptNoEmitOnError> --noEmitOnError
323
typescript
MSBuild
Config TSC Flag
Name
Additional Flags
Because the MSBuild system passes arguments directly to the TypeScript CLI, you can use the option
TypeScriptAdditionalFlags to provide specific flags which don't have a mapping above.
<TypeScriptAdditionalFlags> $(TypeScriptAdditionalFlags) --
noPropertyAccessFromIndexSignature</TypeScriptAdditionalFlags>
<Import
Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\
Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\T
/>
ToolsVersion
The value of <TypeScriptToolsVersion>1.7</TypeScriptToolsVersion> property in the project file
identifies the compiler version to use to build (1.7 in this example). This allows a project to build against the
same versions of the compiler on different machines.
If TypeScriptToolsVersion is not specified, the latest compiler version installed on the machine will be
used to build.
Users using newer versions of TS, will see a prompt to upgrade their project on first load.
324
Compiler Options in MSBuild
TypeScriptCompileBlocked
If you are using a different build tool to build your project (e.g. gulp, grunt , etc.) and VS for the develop-
ment and debugging experience, set <TypeScriptCompileBlocked>true</TypeScriptCompileBlocked>
in your project. This should give you all the editing support, but not the build when you hit F5.
Go to TOC
325
typescript
Compiler supports configuring how to watch files and directories using compiler flags in TypeScript 3.8+,
and environment variables before that.
Background
The --watch implementation of the compiler relies on using fs.watch and fs.watchFile which are pro-
vided by node, both of these methods have pros and cons.
fs.watch uses file system events to notify the changes in the file/directory. But this is OS dependent and
the notification is not completely reliable and does not work as expected on many OS. Also there could be
limit on number of watches that can be created, e.g. linux and we could exhaust it pretty quickly with pro-
grams that include large number of files. But because this uses file system events, there is not much CPU
cycle involved. Compiler typically uses fs.watch to watch directories (e.g. source directories included by
config file, directories in which module resolution failed etc) These can handle the missing precision in noti-
fying about the changes. But recursive watching is supported on only Windows and OSX. That means we
need something to replace the recursive nature on other OS.
fs.watchFile uses polling and thus involves CPU cycles. However, fs.watchFile is the most reliable
mechanism to get the update on the status of file/directory. The compiler typically uses fs.watchFile to
watch source files, config files and missing files (missing file references). This means the CPU usage when
using fs.watchFile depends on number of files in the program.
326
Configuring Watch
327
typescript
Option Description
Go to TOC
328
Integrating with Build Tools
Babel
Install
npm install @babel/cli @babel/core @babel/preset-typescript --save-dev
.babelrc
{
"presets": ["@babel/preset-typescript"]
}
package.json
{
"scripts": {
"build": "babel --out-file bundle.js main.ts"
},
}
Browserify
Install
npm install tsify
Using API
var browserify = require("browserify");
var tsify = require("tsify");
browserify()
.add("main.ts")
.plugin("tsify", { noImplicitAny: true })
.bundle()
.pipe(process.stdout);
329
typescript
Grunt
Install
npm install grunt-ts
Basic Gruntfile.js
module.exports = function (grunt) {
grunt.initConfig({
ts: {
default: {
src: ["**/*.ts", "!node_modules/**/*.ts"],
},
},
});
grunt.loadNpmTasks("grunt-ts");
grunt.registerTask("default", ["ts"]);
};
Gulp
Install
npm install gulp-typescript
Basic gulpfile.js
var gulp = require("gulp");
var ts = require("gulp-typescript");
gulp.task("default", function () {
var tsResult = gulp.src("src/*.ts").pipe(
ts({
noImplicitAny: true,
out: "output.js",
})
);
return tsResult.js.pipe(gulp.dest("built/local"));
});
Jspm
Install
npm install -g jspm@beta
330
Integrating with Build Tools
MSBuild
Update project file to include locally installed Microsoft.TypeScript.Default.props (at the top) and
Microsoft.TypeScript.targets (at the bottom) files:
Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\
Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\T
/>
Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\
Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\T
/>
</Project>
More details about defining MSBuild compiler options: Setting Compiler Options in MSBuild projects
NuGet
Right-Click -> Manage NuGet Packages
Search for Microsoft.TypeScript.MSBuild
Hit Install
When install is complete, rebuild!
More details can be found at Package Manager Dialog and using nightly builds with NuGet
Rollup
Install
npm install @rollup/plugin-typescript --save-dev
331
typescript
Note that both typescript and tslib are peer dependencies of this plugin that need to be installed
separately.
Usage
Create a rollup.config.js configuration file and import the plugin:
// rollup.config.js
import typescript from '@rollup/plugin-typescript';
export default {
input: 'src/index.ts',
output: {
dir: 'output',
format: 'cjs'
},
plugins: [typescript()]
};
Svelte Compiler
Install
npm install --save-dev svelte-preprocess
Note that typescript is an optional peer dependencies of this plugin and needs to be installed separately.
tslib is not provided either.
Usage
Create a svelte.config.js configuration file and import the plugin:
// svelte.config.js
import preprocess from 'svelte-preprocess';
const config = {
// Consult https://github.com/sveltejs/svelte-preprocess
// for more information about preprocessors
preprocess: preprocess()
};
You can now specify that script blocks are written in TypeScript:
<script lang="ts">
Vite
Vite supports importing .ts files out-of-the-box. It only performs transpilation and not type checking. It
also requires that some compilerOptions have certain values. See the Vite docs for more details.
332
Integrating with Build Tools
Webpack
Install
npm install ts-loader --save-dev
module.exports = {
entry: './src/index.ts',
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: /node_modules/,
},
],
},
resolve: {
extensions: ['.tsx', '.ts', '.js'],
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
},
};
Alternatives:
awesome-typescript-loader
Go to TOC
333
typescript
Project references are a new feature in TypeScript 3.0 that allow you to structure your TypeScript programs
into smaller pieces.
By doing this, you can greatly improve build times, enforce logical separation between components, and or-
ganize your code in new and better ways.
We're also introducing a new mode for tsc , the --build flag, that works hand in hand with project refer-
ences to enable faster TypeScript builds.
An Example Project
Let's look at a fairly normal program and see how project references can help us better organize it. Imagine
you have a project with two modules, converter and units , and a corresponding test file for each:
/
├── src/
│ ├── converter.ts
│ └── units.ts
├── test/
│ ├── converter-tests.ts
│ └── units-tests.ts
└── tsconfig.json
The test files import the implementation files and do some testing:
// converter-tests.ts
import * as converter from "../converter";
assert.areEqual(converter.celsiusToFahrenheit(0), 32);
Previously, this structure was rather awkward to work with if you used a single tsconfig file:
It was possible for the implementation files to import the test files
It wasn't possible to build test and src at the same time without having src appear in the output
folder name, which you probably don't want
Changing just the internals in the implementation files required typechecking the tests again, even
though this wouldn't ever cause new errors
Changing just the tests required typechecking the implementation again, even if nothing changed
You could use multiple tsconfig files to solve some of those problems, but new ones would appear:
There's no built-in up-to-date checking, so you end up always running tsc twice
Invoking tsc twice incurs more startup time overhead
tsc -w can't run on multiple config files at once
334
Project References
{
"compilerOptions": {
// The usual
},
"references": [
{ "path": "../src" }
]
}
The path property of each reference can point to a directory containing a tsconfig.json file, or to the
config file itself (which may have any name).
Importing modules from a referenced project will instead load its output declaration file ( .d.ts )
If the referenced project produces an outFile , the output file .d.ts file's declarations will be visible in
this project
Build mode (see below) will automatically build the referenced project if needed
By separating into multiple projects, you can greatly improve the speed of typechecking and compiling, re-
duce memory usage when using an editor, and improve enforcement of the logical groupings of your
program.
composite
Referenced projects must have the new composite setting enabled. This setting is needed to ensure
TypeScript can quickly determine where to find the outputs of the referenced project. Enabling the compos‐
ite flag changes a few things:
The rootDir setting, if not explicitly set, defaults to the directory containing the tsconfig file
All implementation files must be matched by an include pattern or listed in the files array. If this
constraint is violated, tsc will inform you which files weren't specified
declaration must be turned on
declarationMap s
We've also added support for declaration source maps. If you enable declarationMap , you'll be able to use
editor features like "Go to Definition" and Rename to transparently navigate and edit code across project
boundaries in supported editors.
335
typescript
"references": [
{ "path": "../utils", "prepend": true }
]
Prepending a project will include the project's output above the output of the current project. All output files
( .js , .d.ts , .js.map , .d.ts.map ) will be emitted correctly.
tsc will only ever use existing files on disk to do this process, so it's possible to create a project where a
correct output file can't be generated because some project's output would be present more than once in
the resulting file. For example:
A
^ ^
/ \
B C
^ ^
\ /
D
It's important in this situation to not prepend at each reference, because you'll end up with two copies of A
in the output of D - this can lead to unexpected results.
Because dependent projects make use of .d.ts files that are built from their dependencies, you'll either
have to check in certain build outputs or build a project after cloning it before you can navigate the project
in an editor without seeing spurious errors.
When using VS Code (since TS 3.7) we have a behind-the-scenes in-memory .d.ts generation process
that should be able to mitigate this, but it has some perf implications. For very large composite projects you
might want to disable this using disableSourceOfProjectReferenceRedirect option.
Additionally, to preserve compatibility with existing build workflows, tsc will not automatically build depen-
dencies unless invoked with the --build switch. Let's learn more about --build .
336
Project References
You can provide tsc -b with multiple config file paths (e.g. tsc -b src test ). Just like tsc -p , speci-
fying the config file name itself is unnecessary if it's named tsconfig.json .
tsc -b Commandline
You can specify any number of config files:
Don't worry about ordering the files you pass on the commandline - tsc will re-order them if needed so
that dependencies are always built first.
--verbose : Prints out verbose logging to explain what's going on (may be combined with any other
flag)
--dry : Shows what would be done but doesn't actually build anything
--clean : Deletes the outputs of the specified projects (may be combined with --dry )
--force : Act as if all projects are out of date
--watch : Watch mode (may not be combined with any flag except --verbose )
Caveats
Normally, tsc will produce outputs ( .js and .d.ts ) in the presence of syntax or type errors, unless
noEmitOnError is on. Doing this in an incremental build system would be very bad - if one of your out-of-
date dependencies had a new error, you'd only see it once because a subsequent build would skip building
the now up-to-date project. For this reason, tsc -b effectively acts as if noEmitOnError is enabled for all
projects.
If you check in any build outputs ( .js , .d.ts , .d.ts.map , etc.), you may need to run a --force build
after certain source control operations depending on whether your source control tool preserves timestamps
between the local copy and the remote copy.
MSBuild
If you have an msbuild project, you can enable build mode by adding
<TypeScriptBuildMode>true</TypeScriptBuildMode>
to your proj file. This will enable automatic incremental build as well as cleaning.
Note that as with tsconfig.json / -p , existing TypeScript project properties will not be respected - all
settings should be managed using your tsconfig file.
337
typescript
Some teams have set up msbuild-based workflows wherein tsconfig files have the same implicit graph or-
dering as the managed projects they are paired with. If your solution is like this, you can continue to use
msbuild with tsc -p along with project references; these are fully interoperable.
Guidance
Overall Structure
With more tsconfig.json files, you'll usually want to use Configuration file inheritance to centralize your
common compiler options. This way you can change a setting in one file rather than having to edit multiple
files.
Another good practice is to have a "solution" tsconfig.json file that simply has references to all of
your leaf-node projects and sets files to an empty array (otherwise the solution file will cause double
compilation of files). Note that starting with 3.0, it is no longer an error to have an empty files array if
you have at least one reference in a tsconfig.json file.
This presents a simple entry point; e.g. in the TypeScript repo we simply run tsc -b src to build all end-
points because we list all the subprojects in src/tsconfig.json
You can see these patterns in the TypeScript repo - see src/tsconfig_base.json , src/tsconfig.json ,
and src/tsc/tsconfig.json as key examples.
Go to TOC
338
What is a tsconfig.json
Overview
The presence of a tsconfig.json file in a directory indicates that the directory is the root of a TypeScript
project. The tsconfig.json file specifies the root files and the compiler options required to compile the
project.
JavaScript projects can use a jsconfig.json file instead, which acts almost the same but has some
JavaScript-related compiler flags enabled by default.
When input files are specified on the command line, tsconfig.json files are ignored.
Examples
Example tsconfig.json files:
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"sourceMap": true
},
"files": [
"core.ts",
"sys.ts",
"types.ts",
"scanner.ts",
"parser.ts",
"utilities.ts",
"binder.ts",
"checker.ts",
"emitter.ts",
"program.ts",
"commandLineParser.ts",
"tsc.ts",
"diagnosticInformationMap.generated.ts"
]
}
339
typescript
{
"compilerOptions": {
"module": "system",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"outFile": "../../built/local/tsc.js",
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
TSConfig Bases
Depending on the JavaScript runtime environment which you intend to run your code in, there may be a
base configuration which you can use at github.com/tsconfig/bases. These are tsconfig.json files which
your project extends from which simplifies your tsconfig.json by handling the runtime support.
For example, if you were writing a project which uses Node.js version 12 and above, then you could use the
npm module @tsconfig/node12 :
{
"extends": "@tsconfig/node12/tsconfig.json",
"compilerOptions": {
"preserveConstEnums": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
This lets your tsconfig.json focus on the unique choices for your project, and not all of the runtime me-
chanics. There are a few tsconfig bases already, and we're hoping the community can add more for different
environments.
Recommended
Node 10
Node 12
Node 14
Node 16
Deno
React Native
Svelte
340
What is a tsconfig.json
Details
The "compilerOptions" property can be omitted, in which case the compiler's defaults are used. See our
full list of supported Compiler Options.
TSConfig Reference
To learn more about the hundreds of configuration options in the TSConfig Reference.
Schema
The tsconfig.json Schema can be found at the JSON Schema Store.
Go to TOC
341
typescript
This page lists some of the more advanced ways in which you can model types, it works in tandem with the
Utility Types doc which includes types which are included in TypeScript and available globally.
// @errors: 2339
type Fish = { swim: () => void };
type Bird = { fly: () => void };
declare function getSmallPet(): Fish | Bird;
// ---cut---
let pet = getSmallPet();
To get the same code working via property accessors, we'll need to use a type assertion:
if (fishPet.swim) {
fishPet.swim();
} else if (birdPet.fly) {
birdPet.fly();
}
This isn't the sort of code you would want in your codebase however.
It just so happens that TypeScript has something called a type guard. A type guard is some expression that
performs a runtime check that guarantees the type in some scope.
342
Advanced Types
pet is Fish is our type predicate in this example. A predicate takes the form parameterName is Type ,
where parameterName must be the name of a parameter from the current function signature.
Any time isFish is called with some variable, TypeScript will narrow that variable to that specific type if
the original type is compatible.
if (isFish(pet)) {
pet.swim();
} else {
pet.fly();
}
Notice that TypeScript not only knows that pet is a Fish in the if branch; it also knows that in the
else branch, you don't have a Fish , so you must have a Bird .
You may use the type guard isFish to filter an array of Fish | Bird and obtain an array of Fish :
// @errors: 2345
type Fish = { swim: () => void };
type Bird = { fly: () => void };
declare function getSmallPet(): Fish | Bird;
function isFish(pet: Fish | Bird): pet is Fish {
return (pet as Fish).swim !== undefined;
}
// ---cut---
const zoo: (Fish | Bird)[] = [getSmallPet(), getSmallPet(), getSmallPet()];
const underWater1: Fish[] = zoo.filter(isFish);
// or, equivalently
const underWater2: Fish[] = zoo.filter<Fish>(isFish);
const underWater3: Fish[] = zoo.filter<Fish>((pet) => isFish(pet));
343
typescript
For a n in x expression, where n is a string literal or string literal type and x is a union type, the "true"
branch narrows to types which have an optional or required property n , and the "false" branch narrows to
types which have an optional or missing property n .
However, having to define a function to figure out if a type is a primitive is kind of a pain. Luckily, you don't
need to abstract typeof x === "number" into its own function because TypeScript will recognize it as a
type guard on its own. That means we could just write these checks inline.
344
Advanced Types
These typeof type guards are recognized in two different forms: typeof v === "typename" and typeof
v !== "typename" , where "typename" can be one of typeof operator's return values ( "undefined" ,
"number" , "string" , "boolean" , "bigint" , "symbol" , "object" , or "function" ). While
TypeScript won't stop you from comparing to other strings, the language won't recognize those expressions
as type guards.
instanceof type guards are a way of narrowing types using their constructor function. For instance, let's
borrow our industrial strength string-padder example from earlier:
interface Padder {
getPaddingString(): string;
}
function getRandomPadder() {
return Math.random() < 0.5
? new SpaceRepeatingPadder(4)
: new StringPadder(" ");
}
The right side of the instanceof needs to be a constructor function, and TypeScript will narrow down to:
1. the type of the function's prototype property if its type is not any
2. the union of types returned by that type's construct signatures
345
typescript
in that order.
Nullable types
TypeScript has two special types, null and undefined , that have the values null and undefined respec-
tively. We mentioned these briefly in the Basic Types section.
By default, the type checker considers null and undefined assignable to anything. Effectively, null and
undefined are valid values of every type. That means it's not possible to stop them from being assigned to
any type, even when you would like to prevent it. The inventor of null , Tony Hoare, calls this his "billion
dollar mistake".
The strictNullChecks flag fixes this: when you declare a variable, it doesn't automatically include null
or undefined . You can include them explicitly using a union type:
// @errors: 2322
let exampleString = "foo";
exampleString = null;
stringOrNull = undefined;
Note that TypeScript treats null and undefined differently in order to match JavaScript semantics.
string | null is a different type than string | undefined and string | undefined | null .
From TypeScript 3.7 and onwards, you can use optional chaining to simplify working with nullable types.
// @errors: 2345
function f(x: number, y?: number) {
return x + (y ?? 0);
}
f(1, 2);
f(1);
f(1, undefined);
f(1, null);
// @strict: false
// @strictNullChecks: true
// @errors: 2322
class C {
a: number;
b?: number;
}
346
Advanced Types
c.a = 12;
c.a = undefined;
c.b = 13;
c.b = undefined;
c.b = null;
The null elimination is pretty obvious here, but you can use terser operators too:
In cases where the compiler can't eliminate null or undefined , you can use the type assertion operator
to manually remove them. The syntax is postfix ! : identifier! removes null and undefined from
the type of identifier :
// @errors: 2532
function getUser(id: string): UserAccount | undefined {
return {} as any;
}
// ---cut---
interface UserAccount {
id: number;
email?: string;
}
if (user) {
user.email.length;
}
// Instead if you are sure that these objects or fields exist, the
// postfix ! lets you short circuit the nullability
user!.email!.length;
Type Aliases
Type aliases create a new name for a type. Type aliases are sometimes similar to interfaces, but can name
primitives, unions, tuples, and any other types that you'd otherwise have to write by hand.
347
typescript
Aliasing doesn't actually create a new type - it creates a new name to refer to that type. Aliasing a primitive
is not terribly useful, though it can be used as a form of documentation.
Just like interfaces, type aliases can also be generic - we can just add type parameters and use them on the
right side of the alias declaration:
type Tree<T> = {
value: T;
left?: Tree<T>;
right?: Tree<T>;
};
Together with intersection types, we can make some pretty mind-bending types:
interface Person {
name: string;
}
Almost all features of an interface are available in type , the key distinction is that a type cannot be re-
opened to add new properties vs an interface which is always extendable.
Interface Type
348
Advanced Types
Adding new fields to an existing interface A type cannot be changed after being created
Because an interface more closely maps how JavaScript objects work by being open to extension, we rec-
ommend using an interface over a type alias when possible.
On the other hand, if you can't express some shape with an interface and you need to use a union or tuple
type, type aliases are usually the way to go.
349
typescript
Much of the time when we talk about "singleton types", we're referring to both enum member types as well
as numeric/string literal types, though many users will use "singleton types" and "literal types"
interchangeably.
class BasicCalculator {
public constructor(protected value: number = 0) {}
public currentValue(): number {
return this.value;
}
public add(operand: number): this {
this.value += operand;
return this;
}
public multiply(operand: number): this {
this.value *= operand;
return this;
}
// ... other operations go here ...
}
Since the class uses this types, you can extend it and the new class can use the old methods with no
changes.
class BasicCalculator {
public constructor(protected value: number = 0) {}
public currentValue(): number {
return this.value;
}
public add(operand: number): this {
this.value += operand;
return this;
}
public multiply(operand: number): this {
this.value *= operand;
return this;
}
// ... other operations go here ...
}
// ---cut---
class ScientificCalculator extends BasicCalculator {
public constructor(value = 0) {
super(value);
}
public sin() {
this.value = Math.sin(this.value);
return this;
}
// ... other operations go here ...
350
Advanced Types
Without this types, ScientificCalculator would not have been able to extend BasicCalculator and
keep the fluent interface. multiply would have returned BasicCalculator , which doesn't have the sin
method. However, with this types, multiply returns this , which is ScientificCalculator here.
Index types
With index types, you can get the compiler to check code that uses dynamic property names. For example,
a common JavaScript pattern is to pick a subset of properties from an object:
Here's how you would write and use this function in TypeScript, using the index type query and indexed
access operators:
interface Car {
manufacturer: string;
model: string;
year: number;
}
The compiler checks that manufacturer and model are actually properties on Car . The example intro-
duces a couple of new type operators. First is keyof T , the index type query operator. For any type T ,
keyof T is the union of known, public property names of T . For example:
interface Car {
manufacturer: string;
model: string;
year: number;
}
351
typescript
// ---cut---
let carProps: keyof Car;
// ^?
keyof Car is completely interchangeable with "manufacturer" | "model" | "year" . The difference is
that if you add another property to Car , say ownersAddress: string , then keyof Car will automatically
update to be "manufacturer" | "model" | "year" | "ownersAddress" . And you can use keyof in
generic contexts like pluck , where you can't possibly know the property names ahead of time. That means
the compiler will check that you pass the right set of property names to pluck :
The second operator is T[K] , the indexed access operator. Here, the type syntax reflects the expression
syntax. That means that taxi["manufacturer"] has the type Car["manufacturer"] — which in our ex-
ample is just string . However, just like index type queries, you can use T[K] in a generic context, which
is where its real power comes to life. You just have to make sure that the type variable K extends keyof
T . Here's another example with a function named getProperty .
In getProperty , o: T and propertyName: K , so that means o[propertyName]: T[K] . Once you re-
turn the T[K] result, the compiler will instantiate the actual type of the key, so the return type of get‐
Property will vary according to which property you request.
// @errors: 2345
function getProperty<T, K extends keyof T>(o: T, propertyName: K): T[K] {
return o[propertyName]; // o[propertyName] is of type T[K]
}
interface Car {
manufacturer: string;
model: string;
year: number;
}
let taxi: Car = {
manufacturer: "Toyota",
model: "Camry",
year: 2014,
};
// ---cut---
let manufacturer: string = getProperty(taxi, "manufacturer");
let year: number = getProperty(taxi, "year");
352
Advanced Types
interface Dictionary<T> {
[key: string]: T;
}
let keys: keyof Dictionary<number>;
// ^?
let value: Dictionary<number>["foo"];
// ^?
If you have a type with a number index signature, keyof T will just be number .
// @errors: 2339
interface Dictionary<T> {
[key: number]: T;
}
Mapped types
A common task is to take an existing type and make each of its properties optional:
interface PersonSubset {
name?: string;
age?: number;
}
interface PersonReadonly {
readonly name: string;
readonly age: number;
}
This happens often enough in JavaScript that TypeScript provides a way to create new types based on old
types — mapped types. In a mapped type, the new type transforms each property in the old type in the
same way. For example, you can make all properties optional or of a type readonly . Here are a couple of
examples:
type Partial<T> = {
[P in keyof T]?: T[P];
};
353
typescript
// @noErrors
type Readonly<T> = {
readonly [P in keyof T]: T[P];
};
type Person = {
name: string;
age: number;
};
// ---cut---
type PersonPartial = Partial<Person>;
// ^?
type ReadonlyPerson = Readonly<Person>;
// ^?
Note that this syntax describes a type rather than a member. If you want to add members, you can use an
intersection type:
// This is an error!
type WrongPartialWithNewMember<T> = {
[P in keyof T]?: T[P];
newMember: boolean;
}
Let's take a look at the simplest mapped type and its parts:
The syntax resembles the syntax for index signatures with a for .. in inside. There are three parts:
In this simple example, Keys is a hard-coded list of property names and the property type is always bool‐
ean , so this mapped type is equivalent to writing:
type Flags = {
option1: boolean;
option2: boolean;
};
Real applications, however, look like Readonly or Partial above. They're based on some existing type,
and they transform the properties in some way. That's where keyof and indexed access types come in:
354
Advanced Types
type Person = {
name: string;
age: number;
};
// ---cut---
type NullablePerson = { [P in keyof Person]: Person[P] | null };
// ^?
type PartialPerson = { [P in keyof Person]?: Person[P] };
// ^?
In these examples, the properties list is keyof T and the resulting type is some variant of T[P] . This is a
good template for any general use of mapped types. That's because this kind of transformation is homo-
morphic, which means that the mapping applies only to properties of T and no others. The compiler knows
that it can copy all the existing property modifiers before adding any new ones. For example, if
Person.name was readonly, Partial<Person>.name would be readonly and optional.
// @noErrors
type Proxy<T> = {
get(): T;
set(value: T): void;
};
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>;
};
Note that Readonly<T> and Partial<T> are so useful, they are included in TypeScript's standard library
along with Pick and Record :
Readonly , Partial and Pick are homomorphic whereas Record is not. One clue that Record is not
homomorphic is that it doesn't take an input type to copy properties from:
355
typescript
Non-homomorphic types are essentially creating new properties, so they can't copy property modifiers from
anywhere.
Note that keyof any represents the type of any value that can be used as an index to an object. In other-
words, keyof any is currently equal to string | number | symbol .
type Proxy<T> = {
get(): T;
set(value: T): void;
};
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>;
};
Note that this unwrapping inference only works on homomorphic mapped types. If the mapped type is not
homomorphic you'll have to give an explicit type parameter to your unwrapping function.
Conditional Types
A conditional type selects one of two possible types based on a condition expressed as a type relationship
test:
T extends U ? X : Y
The type above means when T is assignable to U the type is X , otherwise the type is Y .
356
Advanced Types
As an example of some types that are immediately resolved, we can take a look at the following example:
declare function f<T extends boolean>(x: T): T extends true ? string : number;
Another example would be the TypeName type alias, which uses nested conditional types:
type T0 = TypeName<string>;
// ^?
type T1 = TypeName<"a">;
// ^?
type T2 = TypeName<true>;
// ^?
type T3 = TypeName<() => void>;
// ^?
type T4 = TypeName<string[]>;
// ^?
But as an example of a place where conditional types are deferred - where they stick around instead of
picking a branch - would be in the following:
interface Foo {
propA: boolean;
propB: boolean;
}
function foo<U>(x: U) {
// Has type 'U extends Foo ? string : number'
let a = f(x);
357
typescript
In the above, the variable a has a conditional type that hasn't yet chosen a branch. When another piece of
code ends up calling foo , it will substitute in U with some other type, and TypeScript will re-evaluate the
conditional type, deciding whether it can actually pick a branch.
In the meantime, we can assign a conditional type to any other target type as long as each branch of the
conditional is assignable to that target. So in our example above we were able to assign U extends Foo ?
string : number to string | number since no matter what the conditional evaluates to, it's known to be
either string or number .
Example
Example
type T1 = Boxed<string>;
// ^?
type T2 = Boxed<number[]>;
358
Advanced Types
// ^?
type T3 = Boxed<string | number[]>;
// ^?
Notice that T has the additional constraint any[] within the true branch of Boxed<T> and it is therefore
possible to refer to the element type of the array as T[number] . Also, notice how the conditional type is
distributed over the union type in the last example.
The distributive property of conditional types can conveniently be used to filter union types:
Conditional types are particularly useful when combined with mapped types:
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
interface Part {
id: number;
359
typescript
name: string;
subparts: Part[];
updatePart(newName: string): void;
}
type T1 = FunctionPropertyNames<Part>;
// ^?
type T2 = NonFunctionPropertyNames<Part>;
// ^?
type T3 = FunctionProperties<Part>;
// ^?
type T4 = NonFunctionProperties<Part>;
// ^?
Note, conditional types are not permitted to reference themselves recursively. For example the following is
an error.
Example
For example, the following extracts the return type of a function type:
// @noErrors
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
Conditional types can be nested to form a sequence of pattern matches that are evaluated in order:
type T0 = Unpacked<string>;
// ^?
type T1 = Unpacked<string[]>;
// ^?
type T2 = Unpacked<() => string>;
// ^?
type T3 = Unpacked<Promise<string>>;
// ^?
type T4 = Unpacked<Promise<string>[]>;
// ^?
type T5 = Unpacked<Unpacked<Promise<string>[]>>;
// ^?
360
Advanced Types
The following example demonstrates how multiple candidates for the same type variable in co-variant posi-
tions causes a union type to be inferred:
Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection
type to be inferred:
type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }
? U
: never;
type T1 = Bar<{ a: (x: string) => void; b: (x: string) => void }>;
// ^?
type T2 = Bar<{ a: (x: string) => void; b: (x: number) => void }>;
// ^?
When inferring from a type with multiple call signatures (such as the type of an overloaded function), infer-
ences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not
possible to perform overload resolution based on a list of argument types.
It is not possible to use infer declarations in constraint clauses for regular type parameters:
However, much the same effect can be obtained by erasing the type variables in the constraint and instead
specifying a conditional type:
// @noErrors
type AnyFunction = (...args: any[]) => any;
type ReturnType<T extends AnyFunction> = T extends (...args: any[]) => infer R
? R
: any;
Go to TOC
361
typescript
Introduction
Some of the unique concepts in TypeScript describe the shape of JavaScript objects at the type level. One
example that is especially unique to TypeScript is the concept of 'declaration merging'. Understanding this
concept will give you an advantage when working with existing JavaScript. It also opens the door to more
advanced abstraction concepts.
For the purposes of this article, "declaration merging" means that the compiler merges two separate decla-
rations declared with the same name into a single definition. This merged definition has the features of both
of the original declarations. Any number of declarations can be merged; it's not limited to just two
declarations.
Basic Concepts
In TypeScript, a declaration creates entities in at least one of three groups: namespace, type, or value.
Namespace-creating declarations create a namespace, which contains names that are accessed using a dot-
ted notation. Type-creating declarations do just that: they create a type that is visible with the declared
shape and bound to the given name. Lastly, value-creating declarations create values that are visible in the
output JavaScript.
Namespace X X
Class X X
Enum X X
Interface X
Type Alias X
Function X
Variable X
Understanding what is created with each declaration will help you understand what is merged when you
perform a declaration merge.
Merging Interfaces
The simplest, and perhaps most common, type of declaration merging is interface merging. At the most ba-
sic level, the merge mechanically joins the members of both declarations into a single interface with the
same name.
interface Box {
height: number;
width: number;
}
362
Declaration Merging
interface Box {
scale: number;
}
Non-function members of the interfaces should be unique. If they are not unique, they must be of the same
type. The compiler will issue an error if the interfaces both declare a non-function member of the same
name, but of different types.
For function members, each function member of the same name is treated as describing an overload of the
same function. Of note, too, is that in the case of interface A merging with later interface A , the second
interface will have a higher precedence than the first.
interface Cloner {
clone(animal: Animal): Animal;
}
interface Cloner {
clone(animal: Sheep): Sheep;
}
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
}
interface Cloner {
clone(animal: Dog): Dog;
clone(animal: Cat): Cat;
clone(animal: Sheep): Sheep;
clone(animal: Animal): Animal;
}
Notice that the elements of each group maintains the same order, but the groups themselves are merged
with later overload sets ordered first.
One exception to this rule is specialized signatures. If a signature has a parameter whose type is a single
string literal type (e.g. not a union of string literals), then it will be bubbled toward the top of its merged
overload list.
interface Document {
createElement(tagName: any): Element;
}
interface Document {
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
}
interface Document {
363
typescript
interface Document {
createElement(tagName: "canvas"): HTMLCanvasElement;
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
createElement(tagName: string): HTMLElement;
createElement(tagName: any): Element;
}
Merging Namespaces
Similarly to interfaces, namespaces of the same name will also merge their members. Since namespaces
create both a namespace and a value, we need to understand how both merge.
To merge the namespaces, type definitions from exported interfaces declared in each namespace are them-
selves merged, forming a single namespace with merged interface definitions inside.
To merge the namespace value, at each declaration site, if a namespace already exists with the given name,
it is further extended by taking the existing namespace and adding the exported members of the second
namespace to the first.
namespace Animals {
export class Zebra {}
}
namespace Animals {
export interface Legged {
numberOfLegs: number;
}
export class Dog {}
}
is equivalent to:
namespace Animals {
export interface Legged {
numberOfLegs: number;
}
This model of namespace merging is a helpful starting place, but we also need to understand what happens
with non-exported members. Non-exported members are only visible in the original (un-merged) name-
space. This means that after merging, merged members that came from other declarations cannot see non-
exported members.
364
Declaration Merging
namespace Animal {
let haveMuscles = true;
namespace Animal {
export function doAnimalsHaveMuscles() {
return haveMuscles; // Error, because haveMuscles is not accessible here
}
}
Because haveMuscles is not exported, only the animalsHaveMuscles function that shares the same un-
merged namespace can see the symbol. The doAnimalsHaveMuscles function, even though it's part of the
merged Animal namespace can not see this un-exported member.
class Album {
label: Album.AlbumLabel;
}
namespace Album {
export class AlbumLabel {}
}
The visibility rules for merged members is the same as described in the Merging Namespaces section, so we
must export the AlbumLabel class for the merged class to see it. The end result is a class managed inside
of another class. You can also use namespaces to add more static members to an existing class.
In addition to the pattern of inner classes, you may also be familiar with the JavaScript practice of creating
a function and then extending the function further by adding properties onto the function. TypeScript uses
declaration merging to build up definitions like this in a type-safe way.
namespace buildLabel {
365
typescript
console.log(buildLabel("Sam Smith"));
enum Color {
red = 1,
green = 2,
blue = 4,
}
namespace Color {
export function mixColor(colorName: string) {
if (colorName == "yellow") {
return Color.red + Color.green;
} else if (colorName == "white") {
return Color.red + Color.green + Color.blue;
} else if (colorName == "magenta") {
return Color.red + Color.blue;
} else if (colorName == "cyan") {
return Color.green + Color.blue;
}
}
}
Disallowed Merges
Not all merges are allowed in TypeScript. Currently, classes can not merge with other classes or with vari-
ables. For information on mimicking class merging, see the Mixins in TypeScript section.
Module Augmentation
Although JavaScript modules do not support merging, you can patch existing objects by importing and then
updating them. Let's look at a toy Observable example:
// observable.ts
export class Observable<T> {
// ... implementation left as an exercise for the reader ...
}
// map.ts
import { Observable } from "./observable";
Observable.prototype.map = function (f) {
// ... another exercise for the reader
};
This works fine in TypeScript too, but the compiler doesn't know about Observable.prototype.map . You
can use module augmentation to tell the compiler about it:
// observable.ts
export class Observable<T> {
// ... implementation left as an exercise for the reader ...
}
366
Declaration Merging
// map.ts
import { Observable } from "./observable";
declare module "./observable" {
interface Observable<T> {
map<U>(f: (x: T) => U): Observable<U>;
}
}
Observable.prototype.map = function (f) {
// ... another exercise for the reader
};
// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map((x) => x.toFixed());
The module name is resolved the same way as module specifiers in import / export . See Modules for
more information. Then the declarations in an augmentation are merged as if they were declared in the
same file as the original.
1. You can't declare new top-level declarations in the augmentation -- just patches to existing declarations.
2. Default exports also cannot be augmented, only named exports (since you need to augment an export
by its exported name, and default is a reserved word - see #14080 for details)
Global augmentation
You can also add declarations to the global scope from inside a module:
// observable.ts
export class Observable<T> {
// ... still no implementation ...
}
declare global {
interface Array<T> {
toObservable(): Observable<T>;
}
}
Array.prototype.toObservable = function () {
// ...
};
Global augmentations have the same behavior and limits as module augmentations.
Go to TOC
367
typescript
Introduction
Further Reading:
A Complete Guide to TypeScript Decorators
With the introduction of Classes in TypeScript and ES6, there now exist certain scenarios that require addi-
tional features to support annotating or modifying classes and class members. Decorators provide a way to
add both annotations and a meta-programming syntax for class declarations and members. Decorators are
a stage 2 proposal for JavaScript and are available as an experimental feature of TypeScript.
NOTE Decorators are an experimental feature that may change in future releases.
To enable experimental support for decorators, you must enable the experimentalDecorators compiler
option either on the command line or in your tsconfig.json :
Command Line:
tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true
}
}
Decorators
A Decorator is a special kind of declaration that can be attached to a class declaration, method, accessor,
property, or parameter. Decorators use the form @expression , where expression must evaluate to a
function that will be called at runtime with information about the decorated declaration.
For example, given the decorator @sealed we might write the sealed function as follows:
function sealed(target) {
// do something with 'target' ...
}
368
Decorators
Decorator Factories
If we want to customize how a decorator is applied to a declaration, we can write a decorator factory. A
Decorator Factory is simply a function that returns the expression that will be called by the decorator at
runtime.
Decorator Composition
Multiple decorators can be applied to a declaration, for example on a single line:
// @experimentalDecorators
// @noErrors
function f() {}
function g() {}
// ---cut---
@f @g x
On multiple lines:
// @experimentalDecorators
// @noErrors
function f() {}
function g() {}
// ---cut---
@f
@g
x
When multiple decorators apply to a single declaration, their evaluation is similar to function composition in
mathematics. In this model, when composing functions f and g, the resulting composite (f ∘ g)(x) is equiva-
lent to f(g(x)).
As such, the following steps are performed when evaluating multiple decorators on a single declaration in
TypeScript:
If we were to use decorator factories, we can observe this evaluation order with the following example:
369
typescript
// @experimentalDecorators
function first() {
console.log("first(): factory evaluated");
return function (target: any, propertyKey: string, descriptor:
PropertyDescriptor) {
console.log("first(): called");
};
}
function second() {
console.log("second(): factory evaluated");
return function (target: any, propertyKey: string, descriptor:
PropertyDescriptor) {
console.log("second(): called");
};
}
class ExampleClass {
@first()
@second()
method() {}
}
Decorator Evaluation
There is a well defined order to how decorators applied to various declarations inside of a class are applied:
1. Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each in-
stance member.
2. Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each static
member.
3. Parameter Decorators are applied for the constructor.
4. Class Decorators are applied for the class.
Class Decorators
A Class Decorator is declared just before a class declaration. The class decorator is applied to the construc-
tor of the class and can be used to observe, modify, or replace a class definition. A class decorator cannot
be used in a declaration file, or in any other ambient context (such as on a declare class).
The expression for the class decorator will be called as a function at runtime, with the constructor of the
decorated class as its only argument.
If the class decorator returns a value, it will replace the class declaration with the provided constructor
function.
370
Decorators
NOTE Should you choose to return a new constructor function, you must take care to maintain the
original prototype. The logic that applies decorators at runtime will not do this for you.
// @experimentalDecorators
function sealed(constructor: Function) {
Object.seal(constructor);
Object.seal(constructor.prototype);
}
// ---cut---
@sealed
class BugReport {
type = "report";
title: string;
constructor(t: string) {
this.title = t;
}
}
We can define the @sealed decorator using the following function declaration:
When @sealed is executed, it will seal both the constructor and its prototype, and will therefore prevent
any further functionality from being added to or removed from this class during runtime by accessing
BugReport.prototype or by defining properties on BugReport itself (note that ES2015 classes are really
just syntactic sugar to prototype-based constructor functions). This decorator does not prevent classes
from sub-classing BugReport .
Next we have an example of how to override the constructor to set new defaults.
// @errors: 2339
// @experimentalDecorators
function reportableClassDecorator<T extends { new (...args: any[]): {} }>
(constructor: T) {
return class extends constructor {
reportingURL = "http://www...";
};
}
@reportableClassDecorator
class BugReport {
type = "report";
title: string;
constructor(t: string) {
this.title = t;
}
}
371
typescript
// Note that the decorator _does not_ change the TypeScript type
// and so the new property `reportingURL` is not known
// to the type system:
bug.reportingURL;
Method Decorators
A Method Decorator is declared just before a method declaration. The decorator is applied to the Property
Descriptor for the method, and can be used to observe, modify, or replace a method definition. A method
decorator cannot be used in a declaration file, on an overload, or in any other ambient context (such as in a
declare class).
The expression for the method decorator will be called as a function at runtime, with the following three
arguments:
1. Either the constructor function of the class for a static member, or the prototype of the class for an in-
stance member.
2. The name of the member.
3. The Property Descriptor for the member.
NOTE The Property Descriptor will be undefined if your script target is less than ES5 .
If the method decorator returns a value, it will be used as the Property Descriptor for the method.
NOTE The return value is ignored if your script target is less than ES5 .
The following is an example of a method decorator ( @enumerable ) applied to a method on the Greeter
class:
// @experimentalDecorators
function enumerable(value: boolean) {
return function (target: any,propertyKey: string,descriptor: PropertyDescriptor)
{
descriptor.enumerable = value;
};
}
// ---cut---
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
372
Decorators
We can define the @enumerable decorator using the following function declaration:
The @enumerable(false) decorator here is a decorator factory. When the @enumerable(false) decorator
is called, it modifies the enumerable property of the property descriptor.
Accessor Decorators
An Accessor Decorator is declared just before an accessor declaration. The accessor decorator is applied to
the Property Descriptor for the accessor and can be used to observe, modify, or replace an accessor's defini-
tions. An accessor decorator cannot be used in a declaration file, or in any other ambient context (such as in
a declare class).
NOTE TypeScript disallows decorating both the get and set accessor for a single member.
Instead, all decorators for the member must be applied to the first accessor specified in document or-
der. This is because decorators apply to a Property Descriptor, which combines both the get and se
t accessor, not each declaration separately.
The expression for the accessor decorator will be called as a function at runtime, with the following three
arguments:
1. Either the constructor function of the class for a static member, or the prototype of the class for an in-
stance member.
2. The name of the member.
3. The Property Descriptor for the member.
NOTE The Property Descriptor will be undefined if your script target is less than ES5 .
If the accessor decorator returns a value, it will be used as the Property Descriptor for the member.
NOTE The return value is ignored if your script target is less than ES5 .
373
typescript
The following is an example of an accessor decorator ( @configurable ) applied to a member of the Point
class:
// @experimentalDecorators
function configurable(value: boolean) {
return function (
target: any,
propertyKey: string,
descriptor: PropertyDescriptor
) {
descriptor.configurable = value;
};
}
// ---cut---
class Point {
private _x: number;
private _y: number;
constructor(x: number, y: number) {
this._x = x;
this._y = y;
}
@configurable(false)
get x() {
return this._x;
}
@configurable(false)
get y() {
return this._y;
}
}
We can define the @configurable decorator using the following function declaration:
Property Decorators
A Property Decorator is declared just before a property declaration. A property decorator cannot be used in
a declaration file, or in any other ambient context (such as in a declare class).
The expression for the property decorator will be called as a function at runtime, with the following two
arguments:
1. Either the constructor function of the class for a static member, or the prototype of the class for an in-
stance member.
2. The name of the member.
374
Decorators
NOTE A Property Descriptor is not provided as an argument to a property decorator due to how
property decorators are initialized in TypeScript. This is because there is currently no mechanism to
describe an instance property when defining members of a prototype, and no way to observe or modi-
fy the initializer for a property. The return value is ignored too. As such, a property decorator can only
be used to observe that a property of a specific name has been declared for a class.
We can use this information to record metadata about the property, as in the following example:
class Greeter {
@format("Hello, %s")
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
let formatString = getFormat(this, "greeting");
return formatString.replace("%s", this.greeting);
}
}
We can then define the @format decorator and getFormat functions using the following function
declarations:
import "reflect-metadata";
The @format("Hello, %s") decorator here is a decorator factory. When @format("Hello, %s") is called,
it adds a metadata entry for the property using the Reflect.metadata function from the reflect-meta‐
data library. When getFormat is called, it reads the metadata value for the format.
NOTE This example requires the reflect-metadata library. See Metadata for more information
about the reflect-metadata library.
375
typescript
Parameter Decorators
A Parameter Decorator is declared just before a parameter declaration. The parameter decorator is applied
to the function for a class constructor or method declaration. A parameter decorator cannot be used in a de-
claration file, an overload, or in any other ambient context (such as in a declare class).
The expression for the parameter decorator will be called as a function at runtime, with the following three
arguments:
1. Either the constructor function of the class for a static member, or the prototype of the class for an in-
stance member.
2. The name of the member.
3. The ordinal index of the parameter in the function's parameter list.
NOTE A parameter decorator can only be used to observe that a parameter has been declared on a
method.
// @experimentalDecorators
function validate(target: any, propertyName: string, descriptor:
TypedPropertyDescriptor<any>) {}
function required(target: Object, propertyKey: string | symbol, parameterIndex:
number) {}
// ---cut---
class BugReport {
type = "report";
title: string;
constructor(t: string) {
this.title = t;
}
@validate
print(@required verbose: boolean) {
if (verbose) {
return `type: ${this.type}\ntitle: ${this.title}`;
} else {
return this.title;
}
}
}
We can then define the @required and @validate decorators using the following function declarations:
// @experimentalDecorators
// @emitDecoratorMetadata
import "reflect-metadata";
const requiredMetadataKey = Symbol("required");
376
Decorators
descriptor.value = function () {
let requiredParameters: number[] = Reflect.getOwnMetadata(requiredMetadataKey,
target, propertyName);
if (requiredParameters) {
for (let parameterIndex of requiredParameters) {
if (parameterIndex >= arguments.length || arguments[parameterIndex] ===
undefined) {
throw new Error("Missing required argument.");
}
}
}
return method.apply(this, arguments);
};
}
The @required decorator adds a metadata entry that marks the parameter as required. The @validate
decorator then wraps the existing greet method in a function that validates the arguments before invoking
the original method.
NOTE This example requires the reflect-metadata library. See Metadata for more information
about the reflect-metadata library.
Metadata
Some examples use the reflect-metadata library which adds a polyfill for an experimental metadata API.
This library is not yet part of the ECMAScript (JavaScript) standard. However, once decorators are officially
adopted as part of the ECMAScript standard these extensions will be proposed for adoption.
TypeScript includes experimental support for emitting certain types of metadata for declarations that have
decorators. To enable this experimental support, you must set the emitDecoratorMetadata compiler op-
tion either on the command line or in your tsconfig.json :
Command Line:
377
typescript
tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
When enabled, as long as the reflect-metadata library has been imported, additional design-time type
information will be exposed at runtime.
// @emitDecoratorMetadata
// @experimentalDecorators
// @strictPropertyInitialization: false
import "reflect-metadata";
class Point {
constructor(public x: number, public y: number) {}
}
class Line {
private _start: Point;
private _end: Point;
@validate
set start(value: Point) {
this._start = value;
}
get start() {
return this._start;
}
@validate
set end(value: Point) {
this._end = value;
}
get end() {
return this._end;
}
}
set.call(this, value);
};
378
Decorators
// @ts-ignore
// line.end = {}
The TypeScript compiler will inject design-time type information using the @Reflect.metadata decorator.
You could consider it the equivalent of the following TypeScript:
class Line {
private _start: Point;
private _end: Point;
@validate
@Reflect.metadata("design:type", Point)
set start(value: Point) {
this._start = value;
}
get start() {
return this._start;
}
@validate
@Reflect.metadata("design:type", Point)
set end(value: Point) {
this._end = value;
}
get end() {
return this._end;
}
}
NOTE Decorator metadata is an experimental feature and may introduce breaking changes in future
releases.
Go to TOC
379
typescript
For the last few years, Node.js has been working to support running ECMAScript modules (ESM). This has
been a very difficult feature to support, since the foundation of the Node.js ecosystem is built on a different
module system called CommonJS (CJS).
Interoperating between the two module systems brings large challenges, with many new features to juggle;
however, support for ESM in Node.js is now implemented in Node.js, and the dust has begun to settle.
That's why TypeScript brings two new module and moduleResolution settings: node16 and nodenext .
{
"compilerOptions": {
"module": "nodenext",
}
}
These new modes bring a few high-level features which we'll explore here.
{
"name": "my-package",
"type": "module",
"//": "...",
"dependencies": {
}
}
This setting controls whether .js files are interpreted as ES modules or CommonJS modules, and defaults
to CommonJS when not set. When a file is considered an ES module, a few different rules come into play
compared to CommonJS:
To overlay the way TypeScript works in this system, .ts and .tsx files now work the same way. When
TypeScript finds a .ts , .tsx , .js , or .jsx file, it will walk up looking for a package.json to see
whether that file is an ES module, and use that to determine:
380
ECMAScript Modules in Node.js
When a .ts file is compiled as an ES module, ECMAScript import / export syntax is left alone in the .js
output; when it's compiled as a CommonJS module, it will produce the same output you get today under
module : commonjs .
This also means paths resolve differently between .ts files that are ES modules and ones that are CJS
modules. For example, let's say you have the following code today:
// ./foo.ts
export function helper() {
// ...
}
// ./bar.ts
import { helper } from "./foo"; // only works in CJS
helper();
This code works in CommonJS modules, but will fail in ES modules because relative import paths need to
use extensions. As a result, it will have to be rewritten to use the extension of the output of foo.ts - so
bar.ts will instead have to import from ./foo.js .
// ./bar.ts
import { helper } from "./foo.js"; // works in ESM & CJS
helper();
This might feel a bit cumbersome at first, but TypeScript tooling like auto-imports and path completion will
typically just do this for you.
One other thing to mention is the fact that this applies to .d.ts files too. When TypeScript finds a .d.ts
file in package, whether it is treated as an ESM or CommonJS file is based on the containing package.
Node.js supports two extensions to help with this: .mjs and .cjs . .mjs files are always ES modules,
and .cjs files are always CommonJS modules, and there's no way to override these.
In turn, TypeScript supports two new source file extensions: .mts and .cts . When TypeScript emits these
to JavaScript files, it will emit them to .mjs and .cjs respectively.
Furthermore, TypeScript also supports two new declaration file extensions: .d.mts and .d.cts . When
TypeScript generates declaration files for .mts and .cts , their corresponding extensions will be .d.mts
and .d.cts .
Using these extensions is entirely optional, but will often be useful even if you choose not to use them as
part of your primary workflow.
381
typescript
CommonJS Interop
Node.js allows ES modules to import CommonJS modules as if they were ES modules with a default export.
// @module: nodenext
// @filename: helper.cts
export function helper() {
console.log("hello world!");
}
// @filename: index.mts
import foo from "./helper.cjs";
In some cases, Node.js also synthesizes named exports from CommonJS modules, which can be more con-
venient. In these cases, ES modules can use a "namespace-style" import (i.e. import * as foo from
"..." ), or named imports (i.e. import { helper } from "..." ).
// @module: nodenext
// @filename: helper.cts
export function helper() {
console.log("hello world!");
}
// @filename: index.mts
import { helper } from "./helper.cjs";
There isn't always a way for TypeScript to know whether these named imports will be synthesized, but
TypeScript will err on being permissive and use some heuristics when importing from a file that is definitely
a CommonJS module.
In a CommonJS module, this just boils down to a require() call, and in an ES module, this imports cre‐
ateRequire to achieve the same thing. This will make code less portable on runtimes like the browser
(which don't support require() ), but will often be useful for interoperability. In turn, you can write the
above example using this syntax as follows:
// @module: nodenext
// @filename: helper.cts
export function helper() {
console.log("hello world!");
}
// @filename: index.mts
import foo = require("./foo.cjs");
foo.helper()
382
ECMAScript Modules in Node.js
Finally, it's worth noting that the only way to import ESM files from a CJS module is using dynamic
import() calls. This can present challenges, but is the behavior in Node.js today.
Here's an package.json that supports separate entry-points for CommonJS and ESM:
// package.json
{
"name": "my-package",
"type": "module",
"exports": {
".": {
// Entry-point for `import "my-package"` in ESM
"import": "./esm/index.js",
There's a lot to this feature, which you can read more about on the Node.js documentation. Here we'll try to
focus on how TypeScript supports it.
With TypeScript's original Node support, it would look for a "main" field, and then look for declaration files
that corresponded to that entry. For example, if "main" pointed to ./lib/index.js , TypeScript would
look for a file called ./lib/index.d.ts . A package author could override this by specifying a separate field
called "types" (e.g. "types": "./types/index.d.ts" ).
The new support works similarly with import conditions. By default, TypeScript overlays the same rules with
import conditions - if you write an import from an ES module, it will look up the import field, and from a
CommonJS module, it will look at the require field. If it finds them, it will look for a colocated declaration
file. If you need to point to a different location for your type declarations, you can add a "types" import
condition.
// package.json
{
"name": "my-package",
"type": "module",
"exports": {
".": {
// Entry-point for TypeScript resolution - must occur first!
"types": "./types/index.d.ts",
383
typescript
TypeScript also supports the "imports" field of package.json in a similar manner (looking for declaration
files alongside corresponding files), and supports packages self-referencing themselves. These features are
generally not as involved, but are supported.
Go to TOC
384
Enums
Enums are one of the few features TypeScript has which is not a type-level extension of JavaScript.
Enums allow a developer to define a set of named constants. Using enums can make it easier to document
intent, or create a set of distinct cases. TypeScript provides both numeric and string-based enums.
Numeric enums
We'll first start off with numeric enums, which are probably more familiar if you're coming from other lan-
guages. An enum can be defined using the enum keyword.
enum Direction {
Up = 1,
Down,
Left,
Right,
}
Above, we have a numeric enum where Up is initialized with 1 . All of the following members are auto-in-
cremented from that point on. In other words, Direction.Up has the value 1 , Down has 2 , Left has
3 , and Right has 4 .
enum Direction {
Up,
Down,
Left,
Right,
}
Here, Up would have the value 0 , Down would have 1 , etc. This auto-incrementing behavior is useful for
cases where we might not care about the member values themselves, but do care that each value is distinct
from other values in the same enum.
Using an enum is simple: just access any member as a property off of the enum itself, and declare types
using the name of the enum:
enum UserResponse {
No = 0,
Yes = 1,
}
Numeric enums can be mixed in computed and constant members (see below). The short story is, enums
without initializers either need to be first, or have to come after numeric enums initialized with numeric con-
stants or other constant enum members. In other words, the following isn't allowed:
385
typescript
// @errors: 1061
const getSomeValue = () => 23;
// ---cut---
enum E {
A = getSomeValue(),
B,
}
String enums
String enums are a similar concept, but have some subtle runtime differences as documented below. In a
string enum, each member has to be constant-initialized with a string literal, or with another string enum
member.
enum Direction {
Up = "UP",
Down = "DOWN",
Left = "LEFT",
Right = "RIGHT",
}
While string enums don't have auto-incrementing behavior, string enums have the benefit that they "serial-
ize" well. In other words, if you were debugging and had to read the runtime value of a numeric enum, the
value is often opaque - it doesn't convey any useful meaning on its own (though reverse mapping can often
help). String enums allow you to give a meaningful and readable value when your code runs, independent
of the name of the enum member itself.
Heterogeneous enums
Technically enums can be mixed with string and numeric members, but it's not clear why you would ever
want to do so:
enum BooleanLikeHeterogeneousEnum {
No = 0,
Yes = "YES",
}
Unless you're really trying to take advantage of JavaScript's runtime behavior in a clever way, it's advised
that you don't do this.
It is the first member in the enum and it has no initializer, in which case it's assigned the value 0 :
// E.X is constant:
enum E {
X,
}
386
Enums
It does not have an initializer and the preceding enum member was a numeric constant. In this case the
value of the current enum member will be the value of the preceding enum member plus one.
enum E1 {
X,
Y,
Z,
}
enum E2 {
A = 1,
B,
C,
}
The enum member is initialized with a constant enum expression. A constant enum expression is a sub-
set of TypeScript expressions that can be fully evaluated at compile time. An expression is a constant
enum expression if it is:
It is a compile time error for constant enum expressions to be evaluated to NaN or Infinity .
enum FileAccess {
// constant members
None,
Read = 1 << 1,
Write = 1 << 2,
ReadWrite = Read | Write,
// computed member
G = "123".length,
}
When all members in an enum have literal enum values, some special semantics come into play.
387
typescript
The first is that enum members also become types as well! For example, we can say that certain members
can only have the value of an enum member:
// @errors: 2322
enum ShapeKind {
Circle,
Square,
}
interface Circle {
kind: ShapeKind.Circle;
radius: number;
}
interface Square {
kind: ShapeKind.Square;
sideLength: number;
}
let c: Circle = {
kind: ShapeKind.Square,
radius: 100,
};
The other change is that enum types themselves effectively become a union of each enum member. With
union enums, the type system is able to leverage the fact that it knows the exact set of values that exist in
the enum itself. Because of that, TypeScript can catch bugs where we might be comparing values
incorrectly. For example:
// @errors: 2367
enum E {
Foo,
Bar,
}
function f(x: E) {
if (x !== E.Foo || x !== E.Bar) {
//
}
}
In that example, we first checked whether x was not E.Foo . If that check succeeds, then our || will
short-circuit, and the body of the 'if' will run. However, if the check didn't succeed, then x can only be
E.Foo , so it doesn't make sense to see whether it's equal to E.Bar .
Enums at runtime
Enums are real objects that exist at runtime. For example, the following enum
enum E {
X,
Y,
Z,
}
388
Enums
enum E {
X,
Y,
Z,
}
enum LogLevel {
ERROR,
WARN,
INFO,
DEBUG,
}
/**
* This is equivalent to:
* type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
*/
type LogLevelStrings = keyof typeof LogLevel;
Reverse mappings
In addition to creating an object with property names for members, numeric enums members also get a re-
verse mapping from enum values to enum names. For example, in this example:
enum Enum {
A,
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
389
typescript
// @showEmit
enum Enum {
A,
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
In this generated code, an enum is compiled into an object that stores both forward ( name -> value ) and
reverse ( value -> name ) mappings. References to other enum members are always emitted as property
accesses and never inlined.
Keep in mind that string enum members do not get a reverse mapping generated at all.
const enums
In most cases, enums are a perfectly valid solution. However sometimes requirements are tighter. To avoid
paying the cost of extra generated code and additional indirection when accessing enum values, it's possible
to use const enums. Const enums are defined using the const modifier on our enums:
Const enums can only use constant enum expressions and unlike regular enums they are completely re-
moved during compilation. Const enum members are inlined at use sites. This is possible since const enums
cannot have computed members.
let directions = [
Direction.Up,
Direction.Down,
Direction.Left,
Direction.Right,
];
// @showEmit
const enum Direction {
Up,
Down,
Left,
Right,
}
390
Enums
let directions = [
Direction.Up,
Direction.Down,
Direction.Left,
Direction.Right,
];
Inlining enum values is straightforward at first, but comes with subtle implications. These pitfalls pertain to
ambient const enums only (basically const enums in .d.ts files) and sharing them between projects, but if
you are publishing or consuming .d.ts files, these pitfalls likely apply to you, because tsc --declara‐
tion transforms .ts files into .d.ts files.
1. For the reasons laid out in the isolatedModules documentation, that mode is fundamentally incompati-
ble with ambient const enums. This means if you publish ambient const enums, downstream consumers
will not be able to use isolatedModules and those enum values at the same time.
2. You can easily inline values from version A of a dependency at compile time, and import version B at
runtime. Version A and B's enums can have different values, if you are not very careful, resulting in sur-
prising bugs, like taking the wrong branches of if statements. These bugs are especially pernicious be-
cause it is common to run automated tests at roughly the same time as projects are built, with the same
dependency versions, which misses these bugs completely.
3. importsNotUsedAsValues: "preserve" will not elide imports for const enums used as values, but am-
bient const enums do not guarantee that runtime .js files exist. The unresolvable imports cause errors
at runtime. The usual way to unambiguously elide imports, type-only imports, does not allow const enum
values, currently.
A. Do not use const enums at all. You can easily ban const enums with the help of a linter. Obviously this
avoids any issues with const enums, but prevents your project from inlining its own enums. Unlike inlining
enums from other projects, inlining a project's own enums is not problematic and has performance implica-
tions. B. Do not publish ambient const enums, by deconstifying them with the help of preserveConst‐
Enums . This is the approach taken internally by the TypeScript project itself. preserveConstEnums emits
the same JavaScript for const enums as plain enums. You can then safely strip the const modifier from
.d.ts files in a build step.
This way downstream consumers will not inline enums from your project, avoiding the pitfalls above, but a
project can still inline its own enums, unlike banning const enums entirely.
Ambient enums
Ambient enums are used to describe the shape of already existing enum types.
391
typescript
One important difference between ambient and non-ambient enums is that, in regular enums, members
that don't have an initializer will be considered constant if its preceding enum member is considered con-
stant. By contrast, an ambient (and non-const) enum member that does not have an initializer is always
considered computed.
Objects vs Enums
In modern TypeScript, you may not need an enum when an object with as const could suffice:
const ODirection = {
Up: 0,
Down: 1,
Left: 2,
Right: 3,
} as const;
EDirection.Up;
// ^?
ODirection.Up;
// ^?
walk(EDirection.Left);
run(ODirection.Right);
The biggest argument in favour of this format over TypeScript's enum is that it keeps your codebase aligned
with the state of JavaScript, and when/if enums are added to JavaScript then you can move to the addition-
al syntax.
Go to TOC
392
Iterators and Generators
Iterables
An object is deemed iterable if it has an implementation for the Symbol.iterator property. Some built-in
types like Array , Map , Set , String , Int32Array , Uint32Array , etc. have their Symbol.iterator
property already implemented. Symbol.iterator function on an object is responsible for returning the list
of values to iterate on.
Iterable interface
Iterable is a type we can use if we want to take in types listed above which are iterable. Here is an
example:
for..of statements
for..of loops over an iterable object, invoking the Symbol.iterator property on the object. Here is a
simple for..of loop on an array:
Another distinction is that for..in operates on any object; it serves as a way to inspect properties on this
object. for..of on the other hand, is mainly interested in values of iterable objects. Built-in objects like
Map and Set implement Symbol.iterator property allowing access to stored values.
393
typescript
Code generation
Targeting ES5 and ES3
When targeting an ES5 or ES3-compliant engine, iterators are only allowed on values of Array type. It is
an error to use for..of loops on non-Array values, even if these non-Array values implement the
Symbol.iterator property.
The compiler will generate a simple for loop for a for..of loop, for instance:
When targeting an ECMAScipt 2015-compliant engine, the compiler will generate for..of loops to target
the built-in iterator implementation in the engine.
Go to TOC
394
JSX
JSX is an embeddable XML-like syntax. It is meant to be transformed into valid JavaScript, though the se-
mantics of that transformation are implementation-specific. JSX rose to popularity with the React
framework, but has since seen other implementations as well. TypeScript supports embedding, type check-
ing, and compiling JSX directly to JavaScript.
Basic usage
In order to use JSX you must do two things.
TypeScript ships with three JSX modes: preserve , react , and react-native . These modes only affect
the emit stage - type checking is unaffected. The preserve mode will keep the JSX as part of the output to
be further consumed by another transform step (e.g. Babel). Additionally the output will have a .jsx file
extension. The react mode will emit React.createElement , does not need to go through a JSX transfor-
mation before use, and the output will have a .js file extension. The react-native mode is the equiva-
lent of preserve in that it keeps all JSX, but the output will instead have a .js file extension.
Output File
Mode Input Output
Extension
<div
preserve <div /> .jsx
/>
<div
react React.createElement("div") .js
/>
react- <div
<div /> .js
native />
<div
react-jsx _jsx("div", {}, void 0); .js
/>
You can specify this mode using either the jsx command line flag or the corresponding option jsx in your
tsconfig.json file.
*Note: You can specify the JSX factory function to use when targeting react JSX emit with jsxFactor
y option (defaults to React.createElement )
395
typescript
The as operator
Recall how to write a type assertion:
This asserts the variable bar to have the type foo . Since TypeScript also uses angle brackets for type as-
sertions, combining it with JSX's syntax would introduce certain parsing difficulties. As a result, TypeScript
disallows angle bracket type assertions in .tsx files.
Since the above syntax cannot be used in .tsx files, an alternate type assertion operator should be used:
as . The example can easily be rewritten with the as operator.
The as operator is available in both .ts and .tsx files, and is identical in behavior to the angle-bracket
type assertion style.
Type Checking
In order to understand type checking with JSX, you must first understand the difference between intrinsic
elements and value-based elements. Given a JSX expression <expr /> , expr may either refer to some-
thing intrinsic to the environment (e.g. a div or span in a DOM environment) or to a custom component
that you've created. This is important for two reasons:
1. For React, intrinsic elements are emitted as strings ( React.createElement("div") ), whereas a compo-
nent you've created is not ( React.createElement(MyComponent) ).
2. The types of the attributes being passed in the JSX element should be looked up differently. Intrinsic ele-
ment attributes should be known intrinsically whereas components will likely want to specify their own
set of attributes.
TypeScript uses the same convention that React does for distinguishing between these. An intrinsic element
always begins with a lowercase letter, and a value-based element always begins with an uppercase letter.
Intrinsic elements
Intrinsic elements are looked up on the special interface JSX.IntrinsicElements . By default, if this inter-
face is not specified, then anything goes and intrinsic elements will not be type checked. However, if this in-
terface is present, then the name of the intrinsic element is looked up as a property on the
JSX.IntrinsicElements interface. For example:
396
JSX
<foo />; // ok
<bar />; // error
In the above example, <foo /> will work fine but <bar /> will result in an error since it has not been
specified on JSX.IntrinsicElements .
Note: You can also specify a catch-all string indexer on JSX.IntrinsicElements as follows:
Value-based elements
Value-based elements are simply looked up by identifiers that are in scope.
<MyComponent />; // ok
<SomeOtherComponent />; // error
Because these two types of value-based elements are indistinguishable from each other in a JSX
expression, first TS tries to resolve the expression as a Function Component using overload resolution. If
the process succeeds, then TS finishes resolving the expression to its declaration. If the value fails to re-
solve as a Function Component, TS will then try to resolve it as a class component. If that fails, TS will re-
port an error.
Function Component
As the name suggests, the component is defined as a JavaScript function where its first argument is a
props object. TS enforces that its return type must be assignable to JSX.Element .
interface FooProp {
name: string;
X: number;
Y: number;
}
397
typescript
Because a Function Component is simply a JavaScript function, function overloads may be used here as
well:
// @noErrors
declare module JSX {
interface Element {}
interface IntrinsicElements {
[s: string]: any;
}
}
// ---cut---
interface ClickableProps {
children: JSX.Element[] | JSX.Element;
}
Note: Function Components were formerly known as Stateless Function Components (SFC). As
Function Components can no longer be considered stateless in recent versions of react, the type SFC
and its alias StatelessComponent were deprecated.
Class Component
It is possible to define the type of a class component. However, to do so it is best to understand two new
terms: the element class type and the element instance type.
Given <Expr /> , the element class type is the type of Expr . So in the example above, if MyComponent
was an ES6 class the class type would be that class's constructor and statics. If MyComponent was a factory
function, the class type would be that function.
Once the class type is established, the instance type is determined by the union of the return types of the
class type's construct or call signatures (whichever is present). So again, in the case of an ES6 class, the in-
stance type would be the type of an instance of that class, and in the case of a factory function, it would be
the type of the value returned from the function.
398
JSX
class MyComponent {
render() {}
}
function MyFactoryFunction() {
return {
render: () => {},
};
}
The element instance type is interesting because it must be assignable to JSX.ElementClass or it will re-
sult in an error. By default JSX.ElementClass is {} , but it can be augmented to limit the use of JSX to
only those types that conform to the proper interface.
class MyComponent {
render() {}
}
function MyFactoryFunction() {
return { render: () => {} };
}
<MyComponent />; // ok
<MyFactoryFunction />; // ok
class NotAValidComponent {}
function NotAValidFactoryFunction() {
return {};
}
399
typescript
For value-based elements, it is a bit more complex. It is determined by the type of a property on the ele-
ment instance type that was previously determined. Which property to use is determined by
JSX.ElementAttributesProperty . It should be declared with a single property. The name of that property
is then used. As of TypeScript 2.8, if JSX.ElementAttributesProperty is not provided, the type of first
parameter of the class element's constructor or Function Component's call will be used instead.
class MyComponent {
// specify the property on the element instance type
props: {
foo?: string;
};
}
The element attribute type is used to type check the attributes in the JSX. Optional and required properties
are supported.
Note: If an attribute name is not a valid JS identifier (like a data-* attribute), it is not considered to
be an error if it is not found in the element attributes type.
400
JSX
Additionally, the JSX.IntrinsicAttributes interface can be used to specify extra properties used by the
JSX framework which are not generally used by the components' props or arguments - for instance key in
React. Specializing further, the generic JSX.IntrinsicClassAttributes<T> type may also be used to
specify the same kind of extra attributes just for class components (and not Function Components). In this
type, the generic parameter corresponds to the class instance type. In React, this is used to allow the ref
attribute of type Ref<T> . Generally speaking, all of the properties on these interfaces should be optional,
unless you intend that users of your JSX framework need to provide some attribute on every tag.
<div>
<h1>Hello</h1>
</div>;
<div>
<h1>Hello</h1>
World
</div>;
You can specify the type of children like any other attribute. This will override the default type from, e.g. the
React typings if you use them.
interface PropsType {
children: JSX.Element
name: string
}
401
typescript
// OK
<Component name="foo">
<h1>Hello World</h1>
</Component>
Embedding Expressions
JSX allows you to embed expressions between tags by surrounding the expressions with curly braces ( {
} ).
const a = (
<div>
{["foo", "bar"].map((i) => (
<span>{i / 2}</span>
))}
</div>
);
The above code will result in an error since you cannot divide a string by a number. The output, when using
the preserve option, looks like:
const a = (
<div>
{["foo", "bar"].map(function (i) {
return <span>{i / 2}</span>;
})}
</div>
);
402
JSX
React integration
To use JSX with React you should use the React typings. These typings define the JSX namespace appro-
priately for use with React.
interface Props {
foo: string;
}
Configuring JSX
There are multiple compiler flags which can be used to customize your JSX, which work as both a compiler
flag and via inline per-file pragmas. To learn more see their tsconfig reference pages:
jsxFactory
jsxFragmentFactory
jsxImportSource
Go to TOC
403
typescript
Along with traditional OO hierarchies, another popular way of building up classes from reusable components
is to build them by combining simpler partial classes. You may be familiar with the idea of mixins or traits
for languages like Scala, and the pattern has also reached some popularity in the JavaScript community.
To get started, we'll need a class which will have the mixins applied on top of:
class Sprite {
name = "";
x = 0;
y = 0;
constructor(name: string) {
this.name = name;
}
}
Then you need a type and a factory function which returns a class expression extending the base class.
setScale(scale: number) {
this._scale = scale;
}
With these all set up, then you can create a class which represents the base class with mixins applied:
class Sprite {
name = "";
x = 0;
y = 0;
404
Mixins
constructor(name: string) {
this.name = name;
}
}
type Constructor = new (...args: any[]) => {};
function Scale<TBase extends Constructor>(Base: TBase) {
return class Scaling extends Base {
// Mixins may not declare private/protected properties
// however, you can use ES2020 private fields
_scale = 1;
setScale(scale: number) {
this._scale = scale;
}
Constrained Mixins
In the above form, the mixin's have no underlying knowledge of the class which can make it hard to create
the design you want.
To model this, we modify the original constructor type to accept a generic argument.
This allows for creating classes which only work with constrained base classes:
constructor(name: string) {
this.name = name;
}
}
// ---cut---
type Positionable = GConstructor<{ setPos: (x: number, y: number) => void }>;
type Spritable = GConstructor<Sprite>;
type Loggable = GConstructor<{ print: () => void }>;
405
typescript
Then you can create mixins which only work when you have a particular base to build on:
constructor(name: string) {
this.name = name;
}
}
type Positionable = GConstructor<{ setPos: (x: number, y: number) => void }>;
type Spritable = GConstructor<Sprite>;
type Loggable = GConstructor<{ print: () => void }>;
// ---cut---
Alternative Pattern
Previous versions of this document recommended a way to write mixins where you created both the runtime
and type hierarchies separately, then merged them at the end:
// @strict: false
// Each mixin is a traditional ES class
class Jumpable {
jump() {}
}
class Duckable {
duck() {}
}
406
Mixins
This pattern relies less on the compiler, and more on your codebase to ensure both runtime and type-sys-
tem are correctly kept in sync.
Constraints
The mixin pattern is supported natively inside the TypeScript compiler by code flow analysis. There are a
few cases where you can hit the edges of the native support.
You cannot use decorators to provide mixins via code flow analysis:
// @experimentalDecorators
// @errors: 2339
// A decorator function which replicates the mixin pattern:
const Pausable = (target: typeof Player) => {
return class Pausable extends target {
shouldFreeze = false;
};
};
@Pausable
class Player {
x = 0;
y = 0;
}
// The Player class does not have the decorator's type merged:
const player = new Player();
player.shouldFreeze;
More of a gotcha than a constraint. The class expression pattern creates singletons, so they can't be
mapped at the type system to support different variable types.
407
typescript
You can work around this by using functions to return your classes which differ based on a generic:
function base<T>() {
class Base {
static prop: T;
}
return Base;
}
function derived<T>() {
class Derived extends base<T>() {
static anotherProp: T;
}
return Derived;
}
Spec.prop; // string
Spec.anotherProp; // string
Go to TOC
408
Module Resolution
This section assumes some basic knowledge about modules. Please see the Modules documentation
for more information.
Module resolution is the process the compiler uses to figure out what an import refers to. Consider an im-
port statement like import { a } from "moduleA" ; in order to check any use of a , the compiler needs
to know exactly what it represents, and will need to check its definition moduleA .
At this point, the compiler will ask "what's the shape of moduleA ?" While this sounds straightforward, mod‐
uleA could be defined in one of your own .ts / .tsx files, or in a .d.ts that your code depends on.
First, the compiler will try to locate a file that represents the imported module. To do so the compiler follows
one of two different strategies: Classic or Node. These strategies tell the compiler where to look for mod‐
uleA .
If that didn't work and if the module name is non-relative (and in the case of "moduleA" , it is), then the
compiler will attempt to locate an ambient module declaration. We'll cover non-relative imports next.
Finally, if the compiler could not resolve the module, it will log an error. In this case, the error would be
something like error TS2307: Cannot find module 'moduleA'.
A relative import is one that starts with / , ./ or ../ . Some examples include:
A relative import is resolved relative to the importing file and cannot resolve to an ambient module declara-
tion. You should use relative imports for your own modules that are guaranteed to maintain their relative lo-
cation at runtime.
A non-relative import can be resolved relative to baseUrl , or through path mapping, which we'll cover be-
low. They can also resolve to ambient module declarations. Use non-relative paths when importing any of
your external dependencies.
409
typescript
Note: node module resolution is the most-commonly used in the TypeScript community and is rec-
ommended for most projects. If you are having resolution problems with import s and export s in
TypeScript, try setting moduleResolution: "node" to see if it fixes the issue.
Classic
This used to be TypeScript's default resolution strategy. Nowadays, this strategy is mainly present for back-
ward compatibility.
A relative import will be resolved relative to the importing file. So import { b } from "./moduleB" in
source file /root/src/folder/A.ts would result in the following lookups:
1. /root/src/folder/moduleB.ts
2. /root/src/folder/moduleB.d.ts
For non-relative module imports, however, the compiler walks up the directory tree starting with the direc-
tory containing the importing file, trying to locate a matching definition file.
For example:
1. /root/src/folder/moduleB.ts
2. /root/src/folder/moduleB.d.ts
3. /root/src/moduleB.ts
4. /root/src/moduleB.d.ts
5. /root/moduleB.ts
6. /root/moduleB.d.ts
7. /moduleB.ts
8. /moduleB.d.ts
Node
This resolution strategy attempts to mimic the Node.js module resolution mechanism at runtime. The full
Node.js resolution algorithm is outlined in Node.js module documentation.
410
Module Resolution
To understand what steps the TS compiler will follow, it is important to shed some light on Node.js modules.
Traditionally, imports in Node.js are performed by calling a function named require . The behavior Node.js
takes will differ depending on if require is given a relative path or a non-relative path.
Relative paths are fairly straightforward. As an example, let's consider a file located at /root/src/module‐
A.js , which contains the import var x = require("./moduleB"); Node.js resolves that import in the fol-
lowing order:
2. Ask the folder /root/src/moduleB if it contains a file named package.json that specifies a "main"
module. In our example, if Node.js found the file /root/src/moduleB/package.json containing {
"main": "lib/mainModule.js" } , then Node.js will refer to /root/src/moduleB/lib/mainModule.js .
3. Ask the folder /root/src/moduleB if it contains a file named index.js . That file is implicitly consid-
ered that folder's "main" module.
You can read more about this in Node.js documentation on file modules and folder modules.
However, resolution for a non-relative module name is performed differently. Node will look for your mod-
ules in special folders named node_modules . A node_modules folder can be on the same level as the cur-
rent file, or higher up in the directory chain. Node will walk up the directory chain, looking through each
node_modules until it finds the module you tried to load.
Following up our example above, consider if /root/src/moduleA.js instead used a non-relative path and
had the import var x = require("moduleB"); . Node would then try to resolve moduleB to each of the
locations until one worked.
1. /root/src/node_modules/moduleB.js
2. /root/src/node_modules/moduleB/package.json (if it specifies a "main" property)
3. /root/src/node_modules/moduleB/index.js
4. /root/node_modules/moduleB.js
5. /root/node_modules/moduleB/package.json (if it specifies a "main" property)
6. /root/node_modules/moduleB/index.js
7. /node_modules/moduleB.js
8. /node_modules/moduleB/package.json (if it specifies a "main" property)
9. /node_modules/moduleB/index.js
You can read more about the process in Node.js documentation on loading modules from node_modules .
411
typescript
TypeScript will mimic the Node.js run-time resolution strategy in order to locate definition files for modules
at compile-time. To accomplish this, TypeScript overlays the TypeScript source file extensions ( .ts , .tsx ,
and .d.ts ) over Node's resolution logic. TypeScript will also use a field in package.json named types
to mirror the purpose of "main" - the compiler will use it to find the "main" definition file to consult.
1. /root/src/moduleB.ts
2. /root/src/moduleB.tsx
3. /root/src/moduleB.d.ts
4. /root/src/moduleB/package.json (if it specifies a types property)
5. /root/src/moduleB/index.ts
6. /root/src/moduleB/index.tsx
7. /root/src/moduleB/index.d.ts
Recall that Node.js looked for a file named moduleB.js , then an applicable package.json , and then for
an index.js .
Similarly, a non-relative import will follow the Node.js resolution logic, first looking up a file, then looking up
an applicable folder. So import { b } from "moduleB" in source file /root/src/moduleA.ts would re-
sult in the following lookups:
1. /root/src/node_modules/moduleB.ts
2. /root/src/node_modules/moduleB.tsx
3. /root/src/node_modules/moduleB.d.ts
4. /root/src/node_modules/moduleB/package.json (if it specifies a types property)
5. /root/src/node_modules/@types/moduleB.d.ts
6. /root/src/node_modules/moduleB/index.ts
7. /root/src/node_modules/moduleB/index.tsx
8. /root/src/node_modules/moduleB/index.d.ts
9. /root/node_modules/moduleB.ts
10. /root/node_modules/moduleB.tsx
11. /root/node_modules/moduleB.d.ts
12. /root/node_modules/moduleB/package.json (if it specifies a types property)
13. /root/node_modules/@types/moduleB.d.ts
14. /root/node_modules/moduleB/index.ts
15. /root/node_modules/moduleB/index.tsx
16. /root/node_modules/moduleB/index.d.ts
17. /node_modules/moduleB.ts
18. /node_modules/moduleB.tsx
412
Module Resolution
19. /node_modules/moduleB.d.ts
20. /node_modules/moduleB/package.json (if it specifies a types property)
21. /node_modules/@types/moduleB.d.ts
22. /node_modules/moduleB/index.ts
23. /node_modules/moduleB/index.tsx
24. /node_modules/moduleB/index.d.ts
Don't be intimidated by the number of steps here - TypeScript is still only jumping up directories twice at
steps (9) and (17). This is really no more complex than what Node.js itself is doing.
The TypeScript compiler has a set of additional flags to inform the compiler of transformations that are ex-
pected to happen to the sources to generate the final output.
It is important to note that the compiler will not perform any of these transformations; it just uses these
pieces of information to guide the process of resolving a module import to its definition file.
Base URL
Using a baseUrl is a common practice in applications using AMD module loaders where modules are "de-
ployed" to a single folder at run-time. The sources of these modules can live in different directories, but a
build script will put them all together.
Setting baseUrl informs the compiler where to find modules. All module imports with non-relative names
are assumed to be relative to the baseUrl .
value of baseUrl command line argument (if given path is relative, it is computed based on current
directory)
value of baseUrl property in 'tsconfig.json' (if given path is relative, it is computed based on the location
of 'tsconfig.json')
Note that relative module imports are not impacted by setting the baseUrl, as they are always resolved rel-
ative to their importing files.
You can find more documentation on baseUrl in RequireJS and SystemJS documentation.
413
typescript
Path mapping
Sometimes modules are not directly located under baseUrl. For instance, an import to a module "jquery"
would be translated at runtime to "node_modules/jquery/dist/jquery.slim.min.js" . Loaders use a
mapping configuration to map module names to files at run-time, see RequireJs documentation and
SystemJS documentation.
The TypeScript compiler supports the declaration of such mappings using paths property in tsconfig.j‐
son files. Here is an example for how to specify the paths property for jquery .
{
"compilerOptions": {
"baseUrl": ".", // This must be specified if "paths" is.
"paths": {
"jquery": ["node_modules/jquery/dist/jquery"] // This mapping is relative to
"baseUrl"
}
}
}
Please notice that paths are resolved relative to baseUrl . When setting baseUrl to a value other than
"." , i.e. the directory of tsconfig.json , the mappings must be changed accordingly. Say, you set
"baseUrl": "./src" in the above example, then jquery should be mapped to
"../node_modules/jquery/dist/jquery" .
Using paths also allows for more sophisticated mappings including multiple fall back locations. Consider a
project configuration where only some modules are available in one location, and the rest are in another. A
build step would put them all together in one place. The project layout may look like:
projectRoot
├── folder1
│ ├── file1.ts (imports 'folder1/file2' and 'folder2/file3')
│ └── file2.ts
├── generated
│ ├── folder1
│ └── folder2
│ └── file3.ts
└── tsconfig.json
{
"compilerOptions": {
"baseUrl": ".",
"paths": {
"*": ["*", "generated/*"]
}
}
}
This tells the compiler for any module import that matches the pattern "*" (i.e. all values), to look in two
locations:
414
Module Resolution
1. "*" : meaning the same name unchanged, so map <moduleName> => <baseUrl>/<moduleName>
2. "generated/*" meaning the module name with an appended prefix "generated", so map
<moduleName> => <baseUrl>/generated/<moduleName>
Following this logic, the compiler will attempt to resolve the two imports as such:
import 'folder1/file2':
1. pattern '*' is matched and wildcard captures the whole module name
2. try first substitution in the list: '*' -> folder1/file2
3. result of substitution is non-relative name - combine it with baseUrl ->
projectRoot/folder1/file2.ts .
4. File exists. Done.
import 'folder2/file3':
1. pattern '*' is matched and wildcard captures the whole module name
2. try first substitution in the list: '*' -> folder2/file3
3. result of substitution is non-relative name - combine it with baseUrl ->
projectRoot/folder2/file3.ts .
4. File does not exist, move to the second substitution
5. second substitution 'generated/*' -> generated/folder2/file3
6. result of substitution is non-relative name - combine it with baseUrl -> projectRoot/generated/fold‐
er2/file3.ts .
7. File exists. Done.
Using rootDirs , you can inform the compiler of the roots making up this "virtual" directory; and thus the
compiler can resolve relative modules imports within these "virtual" directories as if they were merged to-
gether in one directory.
src
└── views
└── view1.ts (imports './template1')
└── view2.ts
generated
└── templates
└── views
└── template1.ts (imports './view2')
415
typescript
Files in src/views are user code for some UI controls. Files in generated/templates are UI template
binding code auto-generated by a template generator as part of the build. A build step will copy the files in
/src/views and /generated/templates/views to the same directory in the output. At run-time, a view
can expect its template to exist next to it, and thus should import it using a relative name as
"./template" .
To specify this relationship to the compiler, use rootDirs . rootDirs specify a list of roots whose contents
are expected to merge at run-time. So following our example, the tsconfig.json file should look like:
{
"compilerOptions": {
"rootDirs": ["src/views", "generated/templates/views"]
}
}
Every time the compiler sees a relative module import in a subfolder of one of the rootDirs , it will at-
tempt to look for this import in each of the entries of rootDirs .
The flexibility of rootDirs is not limited to specifying a list of physical source directories that are logically
merged. The supplied array may include any number of ad hoc, arbitrary directory names, regardless of
whether they exist or not. This allows the compiler to capture sophisticated bundling and runtime features
such as conditional inclusion and project specific loader plugins in a type safe way.
Consider an internationalization scenario where a build tool automatically generates locale specific bundles
by interpolating a special path token, say #{locale} , as part of a relative module path such as ./#{lo‐
cale}/messages . In this hypothetical setup the tool enumerates supported locales, mapping the abstracted
path into ./zh/messages , ./de/messages , and so forth.
Assume that each of these modules exports an array of strings. For example ./zh/messages might
contain:
{
"compilerOptions": {
"rootDirs": ["src/zh", "src/de", "src/#{locale}"]
}
}
The compiler will now resolve import messages from './#{locale}/messages' to import messages
from './zh/messages' for tooling purposes, allowing development in a locale agnostic manner without
compromising design time support.
416
Module Resolution
Let's say we have a sample application that uses the typescript module. app.ts has an import like im‐
port * as ts from "typescript" .
│ tsconfig.json
├───node_modules
│ └───typescript
│ └───lib
│ typescript.d.ts
└───src
app.ts
tsc --traceResolution
417
typescript
Final result
Using --noResolve
Normally the compiler will attempt to resolve all module imports before it starts the compilation process.
Every time it successfully resolves an import to a file, the file is added to the set of files the compiler will
process later on.
The noResolve compiler options instructs the compiler not to "add" any files to the compilation that were
not passed on the command line. It will still try to resolve the module to files, but if the file is not specified,
it will not be included.
For instance:
app.ts
418
Module Resolution
Common Questions
Why does a module in the exclude list still get picked up by the
compiler?
tsconfig.json turns a folder into a “project”. Without specifying any “exclude” or “files” entries, all
files in the folder containing the tsconfig.json and all its sub-directories are included in your compilation.
If you want to exclude some of the files use “exclude” , if you would rather specify all the files instead of
letting the compiler look them up, use “files” .
That was tsconfig.json automatic inclusion. That does not embed module resolution as discussed above.
If the compiler identified a file as a target of a module import, it will be included in the compilation regard-
less if it was excluded in the previous steps.
So to exclude a file from the compilation, you need to exclude it and all files that have an import or ///
<reference path="..." /> directive to it.
Go to TOC
419
typescript
Starting with ECMAScript 2015, JavaScript has a concept of modules. TypeScript shares this concept.
Modules are executed within their own scope, not in the global scope; this means that variables, functions,
classes, etc. declared in a module are not visible outside the module unless they are explicitly exported us-
ing one of the export forms. Conversely, to consume a variable, function, class, interface, etc. exported
from a different module, it has to be imported using one of the import forms.
Modules are declarative; the relationships between modules are specified in terms of imports and exports at
the file level.
Modules import one another using a module loader. At runtime the module loader is responsible for locating
and executing all dependencies of a module before executing it. Well-known module loaders used in
JavaScript are Node.js's loader for CommonJS modules and the RequireJS loader for AMD modules in Web
applications.
In TypeScript, just as in ECMAScript 2015, any file containing a top-level import or export is considered
a module. Conversely, a file without any top-level import or export declarations is treated as a script
whose contents are available in the global scope (and therefore to modules as well).
Export
Exporting a declaration
Any declaration (such as a variable, function, class, type alias, or interface) can be exported by adding the
export keyword.
StringValidator.ts
ZipCodeValidator.ts
Export statements
Export statements are handy when exports need to be renamed for consumers, so the above example can
be written as:
420
Modules
Re-exports
Often modules extend other modules, and partially expose some of their features. A re-export does not im-
port it locally, or introduce a local variable.
ParseIntBasedZipCodeValidator.ts
Optionally, a module can wrap one or more modules and combine all their exports using export * from
"module" syntax.
AllValidators.ts
Import
Importing is just about as easy as exporting from a module. Importing an exported declaration is done
through using one of the import forms below:
421
typescript
import "./my-module.js";
Importing Types
Prior to TypeScript 3.8, you can import a type using import . With TypeScript 3.8, you can import a type
using the import statement, or using import type .
Any explicitly marked type import is guaranteed to be removed from your JavaScript, and tools like Babel
can make better assumptions about your code via the isolatedModules compiler flag. You can read more
in the 3.8 release notes.
Default exports
Each module can optionally export a default export. Default exports are marked with the keyword de‐
fault ; and there can only be one default export per module. default exports are imported using a dif-
ferent import form.
default exports are really handy. For instance, a library like jQuery might have a default export of
jQuery or $ , which we'd probably also import under the name $ or jQuery .
JQuery.d.ts
422
Modules
App.ts
$("button.continue").html("Next Step...");
Classes and function declarations can be authored directly as default exports. Default export class and func-
tion declaration names are optional.
ZipCodeValidator.ts
Test.ts
or
StaticZipCodeValidator.ts
Test.ts
OneTwoThree.ts
Log.ts
console.log(num); // "123"
423
typescript
Export all as x
With TypeScript 3.8, you can use export * as ns as a shorthand for re-exporting another module with a
name:
This takes all of the dependencies from a module and makes it an exported field, you could import it like
this:
They also support replacing the exports object with a custom single object. Default exports are meant to
act as a replacement for this behavior; however, the two are incompatible. TypeScript supports export =
to model the traditional CommonJS and AMD workflow.
The export = syntax specifies a single object that is exported from the module. This can be a class, inter-
face, namespace, function, or enum.
ZipCodeValidator.ts
Test.ts
// Validators to use
let validator = new zip();
424
Modules
This simple example shows how the names used during importing and exporting get translated into the
module loading code.
SimpleModule.ts
import m = require("mod");
export let t = m.something + 1;
UMD SimpleModule.js
(function (factory) {
if (typeof module === "object" && typeof module.exports === "object") {
var v = factory(require, exports);
if (v !== undefined) module.exports = v;
} else if (typeof define === "function" && define.amd) {
define(["require", "exports", "./mod"], factory);
}
})(function (require, exports) {
var mod_1 = require("./mod");
exports.t = mod_1.something + 1;
});
System SimpleModule.js
425
typescript
Simple Example
Below, we've consolidated the Validator implementations used in previous examples to only export a single
named export from each module.
To compile, we must specify a module target on the command line. For Node.js, use --module commonjs ;
for require.js, use --module amd . For example:
When compiled, each module will become a separate .js file. As with reference tags, the compiler will fol-
low import statements to compile dependent files.
Validation.ts
LettersOnlyValidator.ts
ZipCodeValidator.ts
Test.ts
426
Modules
// Validators to use
let validators: { [s: string]: StringValidator } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();
The compiler detects whether each module is used in the emitted JavaScript. If a module identifier is only
ever used as part of a type annotations and never as an expression, then no require call is emitted for
that module. This elision of unused references is a good performance optimization, and also allows for op-
tional loading of those modules.
The core idea of the pattern is that the import id = require("...") statement gives us access to the
types exposed by the module. The module loader is invoked (through require ) dynamically, as shown in
the if blocks below. This leverages the reference-elision optimization so that the module is only loaded
when needed. For this pattern to work, it's important that the symbol defined via an import is only used in
type positions (i.e. never in a position that would be emitted into the JavaScript).
To maintain type safety, we can use the typeof keyword. The typeof keyword, when used in a type posi-
tion, produces the type of a value, in this case the type of the module.
if (needZipValidation) {
let ZipCodeValidator: typeof Zip = require("./ZipCodeValidator");
let validator = new ZipCodeValidator();
if (validator.isAcceptable("...")) {
/* ... */
}
}
427
typescript
if (needZipValidation) {
require(["./ZipCodeValidator"], (ZipCodeValidator: typeof Zip) => {
let validator = new ZipCodeValidator.ZipCodeValidator();
if (validator.isAcceptable("...")) {
/* ... */
}
});
}
if (needZipValidation) {
System.import("./ZipCodeValidator").then((ZipCodeValidator: typeof Zip) => {
var x = new ZipCodeValidator();
if (x.isAcceptable("...")) {
/* ... */
}
});
}
We call declarations that don't define an implementation "ambient". Typically, these are defined in .d.ts
files. If you're familiar with C/C++, you can think of these as .h files. Let's look at a few examples.
Ambient Modules
In Node.js, most tasks are accomplished by loading one or more modules. We could define each module in
its own .d.ts file with top-level export declarations, but it's more convenient to write them as one larger
.d.ts file. To do so, we use a construct similar to ambient namespaces, but we use the module keyword
and the quoted name of the module which will be available to a later import. For example:
428
Modules
Now we can /// <reference> node.d.ts and then load the modules using import url =
require("url"); or import * as URL from "url" .
declarations.d.ts
All imports from a shorthand module will have the any type.
429
typescript
UMD modules
Some libraries are designed to be used in many module loaders, or with no module loading (global
variables). These are known as UMD modules. These libraries can be accessed through either an import or a
global variable. For example:
math-lib.d.ts
It can also be used as a global variable, but only inside of a script. (A script is a file with no imports or
exports.)
mathLib.isPrime(2);
Exporting a namespace from your module is an example of adding too many layers of nesting. While name-
spaces sometime have their uses, they add an extra level of indirection when using modules. This can
quickly become a pain point for users, and is usually unnecessary.
Static methods on an exported class have a similar problem - the class itself adds a layer of nesting. Unless
it increases expressivity or intent in a clearly useful way, consider simply exporting a helper function.
430
Modules
MyClass.ts
MyFunc.ts
Consumer.ts
This is optimal for consumers. They can name your type whatever they want ( t in this case) and don't
have to do any excessive dotting to find your objects.
431
typescript
Consumer.ts
Re-export to extend
Often you will need to extend functionality on a module. A common JS pattern is to augment the original
object with extensions, similar to how JQuery extensions work. As we've mentioned before, modules do not
merge like global namespace objects would. The recommended solution is to not mutate the original object,
but rather export a new entity that provides the new functionality.
Consider a simple calculator implementation defined in module Calculator.ts . The module also exports a
helper function to test the calculator functionality by passing a list of input strings and writing the result at
the end.
Calculator.ts
protected evaluateOperator(
operator: string,
left: number,
right: number
): number {
switch (this.operator) {
case "+":
return left + right;
case "-":
return left - right;
case "*":
return left * right;
case "/":
return left / right;
}
}
private evaluate() {
if (this.operator) {
this.memory = this.evaluateOperator(
this.operator,
this.memory,
432
Modules
this.current
);
} else {
this.memory = this.current;
}
this.current = 0;
}
public getResult() {
return this.memory;
}
}
Here is a simple test for the calculator using the exposed test function.
TestCalculator.ts
Now to extend this to add support for input with numbers in bases other than 10, let's create
ProgrammerCalculator.ts
ProgrammerCalculator.ts
433
typescript
static digits = [
"0",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"A",
"B",
"C",
"D",
"E",
"F",
];
The new module ProgrammerCalculator exports an API shape similar to that of the original Calculator
module, but does not augment any objects in the original module. Here is a test for our
ProgrammerCalculator class:
TestProgrammerCalculator.ts
434
Modules
On the organization front, namespaces are handy for grouping together logically-related objects and types
in the global scope. For example, in C#, you're going to find all the collection types in System.Collections.
By organizing our types into hierarchical namespaces, we provide a good "discovery" experience for users of
those types. Modules, on the other hand, are already present in a file system, necessarily. We have to re-
solve them by path and filename, so there's a logical organization scheme for us to use. We can have a
/collections/generic/ folder with a list module in it.
Namespaces are important to avoid naming collisions in the global scope. For example, you might have
My.Application.Customer.AddForm and My.Application.Order.AddForm -- two types with the same
name, but a different namespace. This, however, is not an issue with modules. Within a module, there's no
plausible reason to have two objects with the same name. From the consumption side, the consumer of any
given module gets to pick the name that they will use to refer to the module, so accidental naming conflicts
are impossible.
For more discussion about modules and namespaces see Namespaces and Modules.
Red Flags
All of the following are red flags for module structuring. Double-check that you're not trying to namespace
your external modules if any of these apply to your files:
A file whose only top-level declaration is export namespace Foo { ... } (remove Foo and move
everything 'up' a level)
Multiple files that have the same export namespace Foo { at top-level (don't think that these are go-
ing to combine into one Foo !)
Go to TOC
435
typescript
A note about terminology: It's important to note that in TypeScript 1.5, the nomenclature has
changed. "Internal modules" are now "namespaces". "External modules" are now simply "modules",
as to align with ECMAScript 2015's terminology, (namely that module X { is equivalent to the now-
preferred namespace X { ).
This post outlines the various ways to organize your code using namespaces (previously "internal modules")
in TypeScript. As we alluded in our note about terminology, "internal modules" are now referred to as
"namespaces". Additionally, anywhere the module keyword was used when declaring an internal module,
the namespace keyword can and should be used instead. This avoids confusing new users by overloading
them with similarly named terms.
First steps
Let's start with the program we'll be using as our example throughout this page. We've written a small set
of simplistic string validators, as you might write to check a user's input on a form in a webpage or check
the format of an externally-provided data file.
// Validators to use
let validators: { [s: string]: StringValidator } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();
436
Namespaces
Namespacing
As we add more validators, we're going to want to have some kind of organization scheme so that we can
keep track of our types and not worry about name collisions with other objects. Instead of putting lots of
different names into the global namespace, let's wrap up our objects into a namespace.
In this example, we'll move all validator-related entities into a namespace called Validation . Because we
want the interfaces and classes here to be visible outside the namespace, we preface them with export .
Conversely, the variables lettersRegexp and numberRegexp are implementation details, so they are left
unexported and will not be visible to code outside the namespace. In the test code at the bottom of the file,
we now need to qualify the names of the types when used outside the namespace, e.g.
Validation.LettersOnlyValidator .
Namespaced Validators
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
// Validators to use
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
437
typescript
);
}
}
Multi-file namespaces
Here, we'll split our Validation namespace across many files. Even though the files are separate, they
can each contribute to the same namespace and can be consumed as if they were all defined in one place.
Because there are dependencies between files, we'll add reference tags to tell the compiler about the rela-
tionships between the files. Our test code is otherwise unchanged.
Validation.ts
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
LettersOnlyValidator.ts
ZipCodeValidator.ts
Test.ts
438
Namespaces
// Validators to use
let validators: { [s: string]: Validation.StringValidator } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
Once there are multiple files involved, we'll need to make sure all of the compiled code gets loaded. There
are two ways of doing this.
First, we can use concatenated output using the outFile option to compile all of the input files into a sin-
gle JavaScript output file:
The compiler will automatically order the output file based on the reference tags present in the files. You
can also specify each file individually:
Alternatively, we can use per-file compilation (the default) to emit one JavaScript file for each input file. If
multiple JS files get produced, we'll need to use <script> tags on our webpage to load each emitted file in
the appropriate order, for example:
MyTestPage.html (excerpt)
Aliases
Another way that you can simplify working with namespaces is to use import q = x.y.z to create shorter
names for commonly-used objects. Not to be confused with the import x = require("name") syntax
used to load modules, this syntax simply creates an alias for the specified symbol. You can use these sorts
of imports (commonly referred to as aliases) for any kind of identifier, including objects created from mod-
ule imports.
namespace Shapes {
export namespace Polygons {
export class Triangle {}
export class Square {}
439
typescript
}
}
Notice that we don't use the require keyword; instead we assign directly from the qualified name of the
symbol we're importing. This is similar to using var , but also works on the type and namespace meanings
of the imported symbol. Importantly, for values, import is a distinct reference from the original symbol, so
changes to an aliased var will not be reflected in the original variable.
We call declarations that don't define an implementation "ambient". Typically these are defined in .d.ts
files. If you're familiar with C/C++, you can think of these as .h files. Let's look at a few examples.
Ambient Namespaces
The popular library D3 defines its functionality in a global object called d3 . Because this library is loaded
through a <script> tag (instead of a module loader), its declaration uses namespaces to define its shape.
For the TypeScript compiler to see this shape, we use an ambient namespace declaration. For example, we
could begin writing it as follows:
declare namespace D3 {
export interface Selectors {
select: {
(selector: string): Selection;
(element: EventTarget): Selection;
};
}
Go to TOC
440
Namespaces and Modules
This post outlines the various ways to organize your code using modules and namespaces in TypeScript.
We'll also go over some advanced topics of how to use namespaces and modules, and address some com-
mon pitfalls when using them in TypeScript.
See the Modules documentation for more information about ES Modules. See the Namespaces documenta-
tion for more information about TypeScript namespaces.
Note: In very old versions of TypeScript namespaces were called 'Internal Modules', these pre-date
JavaScript module systems.
Using Modules
Modules can contain both code and declarations.
Modules also have a dependency on a module loader (such as CommonJs/Require.js) or a runtime which
supports ES Modules. Modules provide for better code reuse, stronger isolation and better tooling support
for bundling.
It is also worth noting that, for Node.js applications, modules are the default and we recommended mod-
ules over namespaces in modern code.
Starting with ECMAScript 2015, modules are native part of the language, and should be supported by all
compliant engine implementations. Thus, for new projects modules would be the recommended code orga-
nization mechanism.
Using Namespaces
Namespaces are a TypeScript-specific way to organize code.
Namespaces are simply named JavaScript objects in the global namespace. This makes namespaces a very
simple construct to use. Unlike modules, they can span multiple files, and can be concatenated using out‐
File . Namespaces can be a good way to structure your code in a Web Application, with all dependencies
included as <script> tags in your HTML page.
Just like all global namespace pollution, it can be hard to identify component dependencies, especially in a
large application.
441
typescript
The compiler will try to find a .ts , .tsx , and then a .d.ts with the appropriate path. If a specific file
could not be found, then the compiler will look for an ambient module declaration. Recall that these need to
be declared in a .d.ts file.
myModules.d.ts
myOtherModule.ts
The reference tag here allows us to locate the declaration file that contains the declaration for the ambient
module. This is how the node.d.ts file that several of the TypeScript samples use is consumed.
Needless Namespacing
If you're converting a program from namespaces to modules, it can be easy to end up with a file that looks
like this:
shapes.ts
The top-level namespace here Shapes wraps up Triangle and Square for no reason. This is confusing
and annoying for consumers of your module:
shapeConsumer.ts
442
Namespaces and Modules
A key feature of modules in TypeScript is that two different modules will never contribute names to the
same scope. Because the consumer of a module decides what name to assign it, there's no need to proac-
tively wrap up the exported symbols in a namespace.
To reiterate why you shouldn't try to namespace your module contents, the general idea of namespacing is
to provide logical grouping of constructs and to prevent name collisions. Because the module file itself is al-
ready a logical grouping, and its top-level name is defined by the code that imports it, it's unnecessary to
use an additional module layer for exported objects.
shapes.ts
shapeConsumer.ts
Trade-offs of Modules
Just as there is a one-to-one correspondence between JS files and modules, TypeScript has a one-to-one
correspondence between module source files and their emitted JS files. One effect of this is that it's not
possible to concatenate multiple module source files depending on the module system you target. For in-
stance, you can't use the outFile option while targeting commonjs or umd , but with TypeScript 1.8 and
later, it's possible to use outFile when targeting amd or system .
Go to TOC
443
typescript
Starting with ECMAScript 2015, symbol is a primitive data type, just like number and string .
Just like strings, symbols can be used as keys for object properties.
let obj = {
[sym]: "value",
};
console.log(obj[sym]); // "value"
Symbols can also be combined with computed property declarations to declare object properties and class
members.
class C {
[getClassNameSymbol]() {
return "C";
}
}
unique symbol
To enable treating symbols as unique literals a special type unique symbol is available. unique symbol is
a subtype of symbol , and are produced only from calling Symbol() or Symbol.for() , or from explicit
type annotations. This type is only allowed on const declarations and readonly static properties, and
in order to reference a specific unique symbol, you’ll have to use the typeof operator. Each reference to a
unique symbol implies a completely unique identity that’s tied to a given declaration.
// @errors: 1332
declare const sym1: unique symbol;
444
Symbols
// Also works.
class C {
static readonly StaticSymbol: unique symbol = Symbol();
}
Because each unique symbol has a completely separate identity, no two unique symbol types are as-
signable or comparable to each other.
// @errors: 2367
const sym2 = Symbol();
const sym3 = Symbol();
Well-known Symbols
In addition to user-defined symbols, there are well-known built-in symbols. Built-in symbols are used to
represent internal language behaviors.
Symbol.hasInstance
A method that determines if a constructor object recognizes an object as one of the constructor’s instances.
Called by the semantics of the instanceof operator.
Symbol.isConcatSpreadable
A Boolean value indicating that an object should be flattened to its array elements by
Array.prototype.concat.
Symbol.iterator
A method that returns the default iterator for an object. Called by the semantics of the for-of statement.
Symbol.match
A regular expression method that matches the regular expression against a string. Called by the
String.prototype.match method.
Symbol.replace
A regular expression method that replaces matched substrings of a string. Called by the
String.prototype.replace method.
445
typescript
Symbol.search
A regular expression method that returns the index within a string that matches the regular expression.
Called by the String.prototype.search method.
Symbol.species
A function valued property that is the constructor function that is used to create derived objects.
Symbol.split
A regular expression method that splits a string at the indices that match the regular expression. Called by
the String.prototype.split method.
Symbol.toPrimitive
A method that converts an object to a corresponding primitive value. Called by the ToPrimitive abstract
operation.
Symbol.toStringTag
A String value that is used in the creation of the default string description of an object. Called by the built-in
method Object.prototype.toString .
Symbol.unscopables
An Object whose own property names are property names that are excluded from the 'with' environment
bindings of the associated objects.
Go to TOC
446
Triple-Slash Directives
Triple-slash directives are single-line comments containing a single XML tag. The contents of the comment
are used as compiler directives.
Triple-slash directives are only valid at the top of their containing file. A triple-slash directive can only be
preceded by single or multi-line comments, including other triple-slash directives. If they are encountered
following a statement or a declaration they are treated as regular single-line comments, and hold no special
meaning.
Triple-slash references instruct the compiler to include additional files in the compilation process.
They also serve as a method to order the output when using out or outFile . Files are emitted to the out-
put file location in the same order as the input after preprocessing pass.
The process starts with a set of root files; these are the file names specified on the command-line or in the
files list in the tsconfig.json file. These root files are preprocessed in the same order they are speci-
fied. Before a file is added to the list, all triple-slash references in it are processed, and their targets includ-
ed. Triple-slash references are resolved in a depth-first manner, in the order they have been seen in the file.
A triple-slash reference path is resolved relative to the containing file, if a relative path is used.
Errors
It is an error to reference a file that does not exist. It is an error for a file to have a triple-slash reference to
itself.
Using --noResolve
If the compiler flag noResolve is specified, triple-slash references are ignored; they neither result in
adding new files, nor change the order of the files provided.
The process of resolving these package names is similar to the process of resolving module names in an
import statement. An easy way to think of triple-slash-reference-types directives are as an import for
declaration packages.
447
typescript
For example, including /// <reference types="node" /> in a declaration file declares that this file uses
names declared in @types/node/index.d.ts ; and thus, this package needs to be included in the compila-
tion along with the declaration file.
Use these directives only when you're authoring a d.ts file by hand.
For declaration files generated during compilation, the compiler will automatically add /// <reference
types="..." /> for you; A /// <reference types="..." /> in a generated declaration file is added if
and only if the resulting file uses any declarations from the referenced package.
For declaring a dependency on an @types package in a .ts file, use types on the command line or in
your tsconfig.json instead. See using @types , typeRoots and types in tsconfig.json files for
more details.
Built-in lib files are referenced in the same fashion as the lib compiler option in tsconfig.json (e.g. use
lib="es2015" and not lib="lib.es2015.d.ts" , etc.).
For declaration file authors who rely on built-in types, e.g. DOM APIs or built-in JS run-time constructors
like Symbol or Iterable , triple-slash-reference lib directives are recommended. Previously these .d.ts
files had to add forward/duplicate declarations of such types.
For example, adding /// <reference lib="es2017.string" /> to one of the files in a compilation is
equivalent to compiling with --lib es2017.string .
"foo".padStart(4);
This directive instructs the compiler to not include the default library (i.e. lib.d.ts ) in the compilation.
The impact here is similar to passing noLib on the command line.
Also note that when passing skipDefaultLibCheck , the compiler will only skip checking files with ///
<reference no-default-lib="true"/> .
448
Triple-Slash Directives
The amd-module directive allows passing an optional module name to the compiler:
amdModule.ts
///<amd-module name="NamedModule"/>
export class C {}
Will result in assigning the name NamedModule to the module as part of calling the AMD define :
amdModule.js
Note: this directive has been deprecated. Use import "moduleName"; statements instead.
/// <amd-dependency path="x" /> informs the compiler about a non-TS module dependency that needs
to be injected in the resulting module's require call.
The amd-dependency directive can also have an optional name property; this allows passing an optional
name for an amd-dependency:
Generated JS code:
Go to TOC
449
typescript
Type compatibility in TypeScript is based on structural subtyping. Structural typing is a way of relating types
based solely on their members. This is in contrast with nominal typing. Consider the following code:
interface Pet {
name: string;
}
class Dog {
name: string;
}
In nominally-typed languages like C# or Java, the equivalent code would be an error because the Dog class
does not explicitly describe itself as being an implementer of the Pet interface.
TypeScript's structural type system was designed based on how JavaScript code is typically written. Because
JavaScript widely uses anonymous objects like function expressions and object literals, it's much more nat-
ural to represent the kinds of relationships found in JavaScript libraries with a structural type system in-
stead of a nominal one.
A Note on Soundness
TypeScript's type system allows certain operations that can't be known at compile-time to be safe. When a
type system has this property, it is said to not be "sound". The places where TypeScript allows unsound be-
havior were carefully considered, and throughout this document we'll explain where these happen and the
motivating scenarios behind them.
Starting out
The basic rule for TypeScript's structural type system is that x is compatible with y if y has at least the
same members as x . For example consider the following code involving an interface named Pet which has
a name property:
interface Pet {
name: string;
}
To check whether dog can be assigned to pet , the compiler checks each property of pet to find a corre-
sponding compatible property in dog . In this case, dog must have a member called name that is a string.
It does, so the assignment is allowed.
The same rule for assignment is used when checking function call arguments:
450
Type Compatibility
interface Pet {
name: string;
}
Note that dog has an extra owner property, but this does not create an error. Only members of the target
type ( Pet in this case) are considered when checking for compatibility.
This comparison process proceeds recursively, exploring the type of each member and sub-member.
y = x; // OK
x = y; // Error
To check if x is assignable to y , we first look at the parameter list. Each parameter in x must have a cor-
responding parameter in y with a compatible type. Note that the names of the parameters are not consid-
ered, only their types. In this case, every parameter of x has a corresponding compatible parameter in y ,
so the assignment is allowed.
The second assignment is an error, because y has a required second parameter that x does not have, so
the assignment is disallowed.
You may be wondering why we allow 'discarding' parameters like in the example y = x . The reason for this
assignment to be allowed is that ignoring extra function parameters is actually quite common in JavaScript.
For example, Array#forEach provides three parameters to the callback function: the array element, its in-
dex, and the containing array. Nevertheless, it's very useful to provide a callback that only uses the first
parameter:
// Should be OK!
items.forEach((item) => console.log(item));
Now let's look at how return types are treated, using two functions that differ only by their return type:
451
typescript
x = y; // OK
y = x; // Error, because x() lacks a location property
The type system enforces that the source function's return type be a subtype of the target type's return
type.
enum EventType {
Mouse,
Keyboard,
}
interface Event {
timestamp: number;
}
interface MyMouseEvent extends Event {
x: number;
y: number;
}
interface MyKeyEvent extends Event {
keyCode: number;
}
// Still disallowed (clear error). Type safety enforced for wholly incompatible
types
listenEvent(EventType.Mouse, (e: number) => console.log(e));
You can have TypeScript raise errors when this happens via the compiler flag strictFunctionTypes .
452
Type Compatibility
When a function has a rest parameter, it is treated as if it were an infinite series of optional parameters.
This is unsound from a type system perspective, but from a runtime point of view the idea of an optional
parameter is generally not well-enforced since passing undefined in that position is equivalent for most
functions.
The motivating example is the common pattern of a function that takes a callback and invokes it with some
predictable (to the programmer) but unknown (to the type system) number of arguments:
Enums
Enums are compatible with numbers, and numbers are compatible with enums. Enum values from different
enum types are considered incompatible. For example,
enum Status {
Ready,
Waiting,
}
enum Color {
Red,
Blue,
Green,
}
453
typescript
Classes
Classes work similarly to object literal types and interfaces with one exception: they have both a static and
an instance type. When comparing two objects of a class type, only members of the instance are compared.
Static members and constructors do not affect compatibility.
class Animal {
feet: number;
constructor(name: string, numFeet: number) {}
}
class Size {
feet: number;
constructor(numFeet: number) {}
}
let a: Animal;
let s: Size;
a = s; // OK
s = a; // OK
Generics
Because TypeScript is a structural type system, type parameters only affect the resulting type when con-
sumed as part of the type of a member. For example,
interface Empty<T> {}
let x: Empty<number>;
let y: Empty<string>;
In the above, x and y are compatible because their structures do not use the type argument in a differen-
tiating way. Changing this example by adding a member to Empty<T> shows how this works:
interface NotEmpty<T> {
data: T;
}
let x: NotEmpty<number>;
let y: NotEmpty<string>;
454
Type Compatibility
In this way, a generic type that has its type arguments specified acts just like a non-generic type.
For generic types that do not have their type arguments specified, compatibility is checked by specifying
any in place of all unspecified type arguments. The resulting types are then checked for compatibility, just
as in the non-generic case.
For example,
identity = reverse; // OK, because (x: any) => any matches (y: any) => any
Advanced Topics
Subtype vs Assignment
So far, we've used "compatible", which is not a term defined in the language spec. In TypeScript, there are
two kinds of compatibility: subtype and assignment. These differ only in that assignment extends subtype
compatibility with rules to allow assignment to and from any , and to and from enum with corresponding
numeric values.
Different places in the language use one of the two compatibility mechanisms, depending on the situation.
For practical purposes, type compatibility is dictated by assignment compatibility, even in the cases of the
implements and extends clauses.
any → ✓ ✓ ✓ ✓ ✓ ✕
unknown → ✓ ✕ ✕ ✕ ✕ ✕
object → ✓ ✓ ✕ ✕ ✕ ✕
void → ✓ ✓ ✕ ✕ ✕ ✕
undefined → ✓ ✓ ✓ ✓ ✓ ✕
null → ✓ ✓ ✓ ✓ ✓ ✕
455
typescript
never → ✓ ✓ ✓ ✓ ✓ ✓
Reiterating The Basics:
Go to TOC
456
Type Inference
In TypeScript, there are several places where type inference is used to provide type information when there
is no explicit type annotation. For example, in this code
let x = 3;
// ^?
The type of the x variable is inferred to be number . This kind of inference takes place when initializing
variables and members, setting parameter default values, and determining function return types.
In most cases, type inference is straightforward. In the following sections, we'll explore some of the nu-
ances in how types are inferred.
To infer the type of x in the example above, we must consider the type of each array element. Here we are
given two choices for the type of the array: number and null . The best common type algorithm considers
each candidate type, and picks the type that is compatible with all the other candidates.
Because the best common type has to be chosen from the provided candidate types, there are some cases
where types share a common structure, but no one type is the super type of all candidate types. For
example:
// @strict: false
class Animal {}
class Rhino extends Animal {
hasHorn: true;
}
class Elephant extends Animal {
hasTrunk: true;
}
class Snake extends Animal {
hasLegs: false;
}
// ---cut---
let zoo = [new Rhino(), new Elephant(), new Snake()];
// ^?
Ideally, we may want zoo to be inferred as an Animal[] , but because there is no object that is strictly of
type Animal in the array, we make no inference about the array element type. To correct this, instead ex-
plicitly provide the type when no one type is a super type of all other candidates:
// @strict: false
class Animal {}
class Rhino extends Animal {
hasHorn: true;
}
457
typescript
When no best common type is found, the resulting inference is the union array type, (Rhino | Elephant |
Snake)[] .
Contextual Typing
Type inference also works in "the other direction" in some cases in TypeScript. This is known as "contextual
typing". Contextual typing occurs when the type of an expression is implied by its location. For example:
// @errors: 2339
window.onmousedown = function (mouseEvent) {
console.log(mouseEvent.button);
console.log(mouseEvent.kangaroo);
};
Here, the TypeScript type checker used the type of the Window.onmousedown function to infer the type of
the function expression on the right hand side of the assignment. When it did so, it was able to infer the
type of the mouseEvent parameter, which does contain a button property, but not a kangaroo property.
This works because window already has onmousedown declared in its type:
// @errors: 2339
window.onscroll = function (uiEvent) {
console.log(uiEvent.button);
};
Based on the fact that the above function is being assigned to Window.onscroll , TypeScript knows that
uiEvent is a UIEvent, and not a MouseEvent like the previous example. UIEvent objects contain no but‐
ton property, and so TypeScript will throw an error.
458
Type Inference
If this function were not in a contextually typed position, the function's argument would implicitly have type
any , and no error would be issued (unless you are using the noImplicitAny option):
// @noImplicitAny: false
const handler = function (uiEvent) {
console.log(uiEvent.button); // <- OK
};
We can also explicitly give type information to the function's argument to override any contextual type:
However, this code will log undefined , since uiEvent has no property called button .
Contextual typing applies in many cases. Common cases include arguments to function calls, right hand
sides of assignments, type assertions, members of object and array literals, and return statements. The
contextual type also acts as a candidate type in best common type. For example:
// @strict: false
class Animal {}
class Rhino extends Animal {
hasHorn: true;
}
class Elephant extends Animal {
hasTrunk: true;
}
class Snake extends Animal {
hasLegs: false;
}
// ---cut---
function createZoo(): Animal[] {
return [new Rhino(), new Elephant(), new Snake()];
}
In this example, best common type has a set of four candidates: Animal , Rhino , Elephant , and Snake .
Of these, Animal can be chosen by the best common type algorithm.
Go to TOC
459
typescript
TypeScript provides several utility types to facilitate common type transformations. These utilities are avail-
able globally.
Awaited<Type>
Released: 4.5
This type is meant to model operations like await in async functions, or the .then() method on
Promise s - specifically, the way that they recursively unwrap Promise s.
Example
type A = Awaited<Promise<string>>;
// ^?
type B = Awaited<Promise<Promise<number>>>;
// ^?
Partial<Type>
Released:
2.1
Constructs a type with all properties of Type set to optional. This utility will return a type that represents
all subsets of a given type.
Example
interface Todo {
title: string;
description: string;
}
const todo1 = {
title: "organize desk",
description: "clear clutter",
};
460
Utility Types
Required<Type>
Released:
2.8
Constructs a type consisting of all properties of Type set to required. The opposite of Partial .
Example
// @errors: 2741
interface Props {
a?: number;
b?: string;
}
Readonly<Type>
Released:
2.1
Constructs a type with all properties of Type set to readonly , meaning the properties of the constructed
type cannot be reassigned.
Example
// @errors: 2540
interface Todo {
title: string;
}
todo.title = "Hello";
This utility is useful for representing assignment expressions that will fail at runtime (i.e. when attempting
to reassign properties of a frozen object).
Object.freeze
461
typescript
Record<Keys, Type>
Released:
2.1
Constructs an object type whose property keys are Keys and whose property values are Type . This utility
can be used to map the properties of a type to another type.
Example
interface CatInfo {
age: number;
breed: string;
}
cats.boris;
// ^?
Pick<Type, Keys>
Released:
2.1
Constructs a type by picking the set of properties Keys (string literal or union of string literals) from Type .
Example
interface Todo {
title: string;
description: string;
completed: boolean;
}
todo;
// ^?
462
Utility Types
Omit<Type, Keys>
Released:
3.5
Constructs a type by picking all properties from Type and then removing Keys (string literal or union of
string literals).
Example
interface Todo {
title: string;
description: string;
completed: boolean;
createdAt: number;
}
todo;
// ^?
todoInfo;
// ^?
Exclude<UnionType, ExcludedMembers>
Released:
2.8
Constructs a type by excluding from UnionType all union members that are assignable to
ExcludedMembers .
463
typescript
Example
Extract<Type, Union>
Released:
2.8
Constructs a type by extracting from Type all union members that are assignable to Union .
Example
NonNullable<Type>
Released:
2.8
Example
Parameters<Type>
Released:
3.1
Constructs a tuple type from the types used in the parameters of a function type Type .
464
Utility Types
Example
// @errors: 2344
declare function f1(arg: { a: number; b: string }): void;
ConstructorParameters<Type>
Released:
3.1
Constructs a tuple or array type from the types of a constructor function type. It produces a tuple type with
all the parameter types (or the type never if Type is not a function).
Example
// @errors: 2344
// @strict: false
type T0 = ConstructorParameters<ErrorConstructor>;
// ^?
type T1 = ConstructorParameters<FunctionConstructor>;
// ^?
type T2 = ConstructorParameters<RegExpConstructor>;
// ^?
type T3 = ConstructorParameters<any>;
// ^?
type T4 = ConstructorParameters<Function>;
// ^?
ReturnType<Type>
Released:
2.8
465
typescript
Example
InstanceType<Type>
Released:
2.8
Example
466
Utility Types
ThisParameterType<Type>
Released:
3.3
Extracts the type of the this parameter for a function type, or unknown if the function type has no this
parameter.
Example
OmitThisParameter<Type>
Released:
3.3
Removes the this parameter from Type . If Type has no explicitly declared this parameter, the result
is simply Type . Otherwise, a new function type with no this parameter is created from Type . Generics
are erased and only the last overload signature is propagated into the new function type.
Example
console.log(fiveToHex());
ThisType<Type>
Released:
2.3
This utility does not return a transformed type. Instead, it serves as a marker for a contextual this type.
Note that the noImplicitThis flag must be enabled to use this utility.
467
typescript
Example
// @noImplicitThis: false
type ObjectDescriptor<D, M> = {
data?: D;
methods?: M & ThisType<D & M>; // Type of 'this' in methods is D & M
};
obj.x = 10;
obj.y = 20;
obj.moveBy(5, 5);
In the example above, the methods object in the argument to makeObject has a contextual type that in-
cludes ThisType<D & M> and therefore the type of this in methods within the methods object is { x:
number, y: number } & { moveBy(dx: number, dy: number): number } . Notice how the type of the
methods property simultaneously is an inference target and a source for the this type in methods.
The ThisType<T> marker interface is simply an empty interface declared in lib.d.ts . Beyond being rec-
ognized in the contextual type of an object literal, the interface acts like any empty interface.
Lowercase<StringType>
Capitalize<StringType>
Uncapitalize<StringType>
To help with string manipulation around template string literals, TypeScript includes a set of types which can
be used in string manipulation within the type system. You can find those in the Template Literal Types
documentation.
Go to TOC
468
Variable Declaration
let and const are two relatively new concepts for variable declarations in JavaScript. As we mentioned
earlier, let is similar to var in some respects, but allows users to avoid some of the common "gotchas"
that users run into in JavaScript.
With TypeScript being an extension of JavaScript, the language naturally supports let and const . Here
we'll elaborate more on these new declarations and why they're preferable to var .
If you've used JavaScript offhandedly, the next section might be a good way to refresh your memory. If
you're intimately familiar with all the quirks of var declarations in JavaScript, you might find it easier to
skip ahead.
var declarations
Declaring a variable in JavaScript has always traditionally been done with the var keyword.
var a = 10;
As you might've figured out, we just declared a variable named a with the value 10 .
function f() {
var message = "Hello, world!";
return message;
}
and we can also access those same variables within other functions:
function f() {
var a = 10;
return function g() {
var b = a + 1;
return b;
};
}
var g = f();
g(); // returns '11'
In this above example, g captured the variable a declared in f . At any point that g gets called, the val-
ue of a will be tied to the value of a in f . Even if g is called once f is done running, it will be able to
access and modify a .
function f() {
var a = 1;
a = 2;
var b = g();
a = 3;
469
typescript
return b;
function g() {
return a;
}
}
Scoping rules
var declarations have some odd scoping rules for those used to other languages. Take the following
example:
return x;
}
Some readers might do a double-take at this example. The variable x was declared within the if block,
and yet we were able to access it from outside that block. That's because var declarations are accessible
anywhere within their containing function, module, namespace, or global scope - all which we'll go over lat-
er on - regardless of the containing block. Some people call this var -scoping or function-scoping.
Parameters are also function scoped.
These scoping rules can cause several types of mistakes. One problem they exacerbate is the fact that it is
not an error to declare the same variable multiple times:
return sum;
}
Maybe it was easy to spot out for some experienced JavaScript developers, but the inner for -loop will ac-
cidentally overwrite the variable i because i refers to the same function-scoped variable. As experienced
developers know by now, similar sorts of bugs slip through code reviews and can be an endless source of
frustration.
470
Variable Declaration
For those unfamiliar, setTimeout will try to execute a function after a certain number of milliseconds
(though waiting for anything else to stop running).
10
10
10
10
10
10
10
10
10
10
Many JavaScript developers are intimately familiar with this behavior, but if you're surprised, you're certain-
ly not alone. Most people expect the output to be
0
1
2
3
4
5
6
7
8
9
Remember what we mentioned earlier about variable capturing? Every function expression we pass to set‐
Timeout actually refers to the same i from the same scope.
Let's take a minute to consider what that means. setTimeout will run a function after some number of mil-
liseconds, but only after the for loop has stopped executing; By the time the for loop has stopped exe-
cuting, the value of i is 10 . So each time the given function gets called, it will print out 10 !
A common work around is to use an IIFE - an Immediately Invoked Function Expression - to capture i at
each iteration:
471
typescript
setTimeout(function () {
console.log(i);
}, 100 * i);
})(i);
}
This odd-looking pattern is actually pretty common. The i in the parameter list actually shadows the i
declared in the for loop, but since we named them the same, we didn't have to modify the loop body too
much.
let declarations
By now you've figured out that var has some problems, which is precisely why let statements were in-
troduced. Apart from the keyword used, let statements are written the same way var statements are.
The key difference is not in the syntax, but in the semantics, which we'll now dive into.
Block-scoping
When a variable is declared using let , it uses what some call lexical-scoping or block-scoping. Unlike vari-
ables declared with var whose scopes leak out to their containing function, block-scoped variables are not
visible outside of their nearest containing block or for -loop.
if (input) {
// Still okay to reference 'a'
let b = a + 1;
return b;
}
Here, we have two local variables a and b . a 's scope is limited to the body of f while b 's scope is limit-
ed to the containing if statement's block.
try {
throw "oh no!";
} catch (e) {
console.log("Oh well.");
}
472
Variable Declaration
Another property of block-scoped variables is that they can't be read or written to before they're actually
declared. While these variables are "present" throughout their scope, all points up until their declaration are
part of their temporal dead zone. This is just a sophisticated way of saying you can't access them before the
let statement, and luckily TypeScript will let you know that.
Something to note is that you can still capture a block-scoped variable before it's declared. The only catch is
that it's illegal to call that function before the declaration. If targeting ES2015, a modern runtime will throw
an error; however, right now TypeScript is permissive and won't report this as an error.
function foo() {
// okay to capture 'a'
return a;
}
let a;
For more information on temporal dead zones, see relevant content on the Mozilla Developer Network.
function f(x) {
var x;
var x;
if (true) {
var x;
}
}
In the above example, all declarations of x actually refer to the same x , and this is perfectly valid. This
often ends up being a source of bugs. Thankfully, let declarations are not as forgiving.
let x = 10;
let x = 20; // error: can't re-declare 'x' in the same scope
The variables don't necessarily need to both be block-scoped for TypeScript to tell us that there's a problem.
function f(x) {
let x = 100; // error: interferes with parameter declaration
}
function g() {
473
typescript
let x = 100;
var x = 100; // error: can't have both declarations of 'x'
}
That's not to say that a block-scoped variable can never be declared with a function-scoped variable. The
block-scoped variable just needs to be declared within a distinctly different block.
function f(condition, x) {
if (condition) {
let x = 100;
return x;
}
return x;
}
The act of introducing a new name in a more nested scope is called shadowing. It is a bit of a double-edged
sword in that it can introduce certain bugs on its own in the event of accidental shadowing, while also pre-
venting certain bugs. For instance, imagine we had written our earlier sumMatrix function using let
variables.
return sum;
}
This version of the loop will actually perform the summation correctly because the inner loop's i shadows
i from the outer loop.
Shadowing should usually be avoided in the interest of writing clearer code. While there are some scenarios
where it may be fitting to take advantage of it, you should use your best judgement.
function theCityThatAlwaysSleeps() {
let getCity;
if (true) {
let city = "Seattle";
474
Variable Declaration
getCity = function () {
return city;
};
}
return getCity();
}
Because we've captured city from within its environment, we're still able to access it despite the fact that
the if block finished executing.
Recall that with our earlier setTimeout example, we ended up needing to use an IIFE to capture the state
of a variable for every iteration of the for loop. In effect, what we were doing was creating a new variable
environment for our captured variables. That was a bit of a pain, but luckily, you'll never have to do that
again in TypeScript.
let declarations have drastically different behavior when declared as part of a loop. Rather than just intro-
ducing a new environment to the loop itself, these declarations sort of create a new scope per iteration.
Since this is what we were doing anyway with our IIFE, we can change our old setTimeout example to just
use a let declaration.
0
1
2
3
4
5
6
7
8
9
const declarations
const declarations are another way of declaring variables.
const numLivesForCat = 9;
They are like let declarations but, as their name implies, their value cannot be changed once they are
bound. In other words, they have the same scoping rules as let , but you can't re-assign to them.
This should not be confused with the idea that the values they refer to are immutable.
475
typescript
const numLivesForCat = 9;
const kitty = {
name: "Aurora",
numLives: numLivesForCat,
};
// Error
kitty = {
name: "Danielle",
numLives: numLivesForCat,
};
// all "okay"
kitty.name = "Rory";
kitty.name = "Kitty";
kitty.name = "Cat";
kitty.numLives--;
Unless you take specific measures to avoid it, the internal state of a const variable is still modifiable.
Fortunately, TypeScript allows you to specify that members of an object are readonly . The chapter on
Interfaces has the details.
Applying the principle of least privilege, all declarations other than those you plan to modify should use
const . The rationale is that if a variable didn't need to get written to, others working on the same code-
base shouldn't automatically be able to write to the object, and will need to consider whether they really
need to reassign to the variable. Using const also makes code more predictable when reasoning about
flow of data.
Use your best judgement, and if applicable, consult the matter with the rest of your team.
Destructuring
Another ECMAScript 2015 feature that TypeScript has is destructuring. For a complete reference, see the ar-
ticle on the Mozilla Developer Network. In this section, we'll give a short overview.
Array destructuring
The simplest form of destructuring is array destructuring assignment:
476
Variable Declaration
This creates two new variables named first and second . This is equivalent to using indexing, but is
much more convenient:
first = input[0];
second = input[1];
// swap variables
[first, second] = [second, first];
You can create a variable for the remaining items in a list using the syntax ... :
Of course, since this is JavaScript, you can just ignore trailing elements you don't care about:
Or other elements:
Tuple destructuring
Tuples may be destructured like arrays; the destructuring variables get the types of the corresponding tuple
elements:
As with arrays, you can destructure the rest of the tuple with ... , to get a shorter tuple:
477
typescript
Object destructuring
You can also destructure objects:
let o = {
a: "foo",
b: 12,
c: "bar",
};
let { a, b } = o;
This creates new variables a and b from o.a and o.b . Notice that you can skip c if you don't need it.
Notice that we had to surround this statement with parentheses. JavaScript normally parses a { as the
start of block.
You can create a variable for the remaining items in an object using the syntax ... :
let { a, ...passthrough } = o;
let total = passthrough.b + passthrough.c.length;
Property renaming
You can also give different names to properties:
Here the syntax starts to get confusing. You can read a: newName1 as " a as newName1 ". The direction is
left-to-right, as if you had written:
Confusingly, the colon here does not indicate the type. The type, if you specify it, still needs to be written
after the entire destructuring:
478
Variable Declaration
Default values
Default values let you specify a default value in case a property is undefined:
In this example the b? indicates that b is optional, so it may be undefined . keepWholeObject now has
a variable for wholeObject as well as the properties a and b , even if b is undefined.
Function declarations
Destructuring also works in function declarations. For simple cases this is straightforward:
But specifying defaults is more common for parameters, and getting defaults right with destructuring can be
tricky. First of all, you need to remember to put the pattern before the default value.
The snippet above is an example of type inference, explained earlier in the handbook.
Then, you need to remember to give a default for optional properties on the destructured property instead
of the main initializer. Remember that C was defined with b optional:
Use destructuring with care. As the previous example demonstrates, anything but the simplest destructur-
ing expression is confusing. This is especially true with deeply nested destructuring, which gets really hard
to understand even without piling on renaming, default values, and type annotations. Try to keep destruc-
turing expressions small and simple. You can always write the assignments that destructuring would gener-
ate yourself.
479
typescript
Spread
The spread operator is the opposite of destructuring. It allows you to spread an array into another array, or
an object into another object. For example:
This gives bothPlus the value [0, 1, 2, 3, 4, 5] . Spreading creates a shallow copy of first and sec‐
ond . They are not changed by the spread.
Now search is { food: "rich", price: "$$", ambiance: "noisy" } . Object spreading is more com-
plex than array spreading. Like array spreading, it proceeds from left-to-right, but the result is still an ob-
ject. This means that properties that come later in the spread object overwrite properties that come earlier.
So if we modify the previous example to spread at the end:
Then the food property in defaults overwrites food: "rich" , which is not what we want in this case.
Object spread also has a couple of other surprising limits. First, it only includes an objects' own, enumerable
properties. Basically, that means you lose methods when you spread instances of an object:
class C {
p = 12;
m() {}
}
let c = new C();
let clone = { ...c };
clone.p; // ok
clone.m(); // error!
Second, the TypeScript compiler doesn't allow spreads of type parameters from generic functions. That fea-
ture is expected in future versions of the language.
Go to TOC
480
TypeScript 1.1
Performance Improvements
The 1.1 compiler is typically around 4x faster than any previous release. See this blog post for some im-
pressive charts.
module MyControllers {
interface ZooScope extends ng.IScope {
animals: Animal[];
}
export class ZooController {
// Used to be an error (cannot expose ZooScope), but now is only
// an error when trying to generate .d.ts files
constructor(public $scope: ZooScope) {}
/* more code */
}
}
Go to TOC
481
typescript
Protected
The new protected modifier in classes works like it does in familiar languages like C++, C#, and Java. A
protected member of a class is visible only inside subclasses of the class in which it is declared:
class Thing {
protected doSomething() {
/* ... */
}
}
Tuple types
Tuple types express an array where the type of certain elements is known, but need not be the same. For
example, you may want to represent an array with a string at position 0 and a number at position 1:
When accessing an element with a known index, the correct type is retrieved:
console.log(x[0].substr(1)); // OK
console.log(x[1].substr(1)); // Error, 'number' does not have 'substr'
Note that in TypeScript 1.4, when accessing an element outside the set of known indices, a union type is
used instead:
x[3] = "world"; // OK
console.log(x[5].toString()); // OK, 'string' and 'number' both have toString
x[6] = true; // Error, boolean isn't number or string
Go to TOC
482
TypeScript 1.4
Union types
Overview
Union types are a powerful way to express a value that can be one of several types. For example, you might
have an API for running a program that takes a commandline as either a string , a string[] or a func-
tion that returns a string . You can now write:
interface RunOptions {
program: string;
commandline: string[] | string | (() => string);
}
Assignment to union types works very intuitively -- anything you could assign to one of the union type's
members is assignable to the union:
When reading from a union type, you can see any properties that are shared by them:
if (opts.commandline.length === 0) {
// OK, string and string[] both have 'length' property
console.log("it's empty");
}
Using Type Guards, you can easily work with a variable of a union type:
Stricter Generics
With union types able to represent a wide range of type scenarios, we've decided to improve the strictness
of certain generic calls. Previously, code like this would (surprisingly) compile without error:
// Previously: No error
// New behavior: Error, no best common type between 'string' and 'number'
var e = equal(42, "hello");
483
typescript
With union types, you can now specify the desired behavior at both the function declaration site and the call
site:
let declarations
In JavaScript, var declarations are "hoisted" to the top of their enclosing scope. This can result in confus-
ing bugs:
The new ES6 keyword let , now supported in TypeScript, declares a variable with more intuitive "block" se-
mantics. A let variable can only be referred to after its declaration, and is scoped to the syntactic block
where it is defined:
if (foo) {
console.log(x); // Error, cannot refer to x before its declaration
let x = "hello";
} else {
console.log(x); // Error, x is not declared in this block
}
484
TypeScript 1.4
const declarations
The other new ES6 declaration type supported in TypeScript is const . A const variable may not be as-
signed to, and must be initialized where it is declared. This is useful for declarations where you don't want
to change the value after its initialization:
Template strings
TypeScript now supports ES6 template strings. These are an easy way to embed arbitrary expressions in
strings:
Type Guards
A common pattern in JavaScript is to use typeof or instanceof to examine the type of an expression at
runtime. TypeScript now understands these conditions and will change type inference accordingly when
used in an if block.
485
typescript
Type Aliases
You can now define an alias for a type using the type keyword:
Type aliases are exactly the same as their original types; they are simply alternative names.
Compiles to exactly:
var d = 1;
enum MyFlags {
None = 0,
Neat = 1,
Cool = 2,
Awesome = 4,
Best = Neat | Cool | Awesome
}
var b = MyFlags.Best; // emits var b = 7;
486
TypeScript 1.4
This is now the default for MSBuild projects; this allows MSBuild incremental build to work as expected, as
outputs are only generated on clean builds.
The new amd-module name tag allows passing an optional module name to the compiler:
//// [amdModule.ts]
///<amd-module name='NamedModule'/>
export class C {}
Will result in assigning the name NamedModule to the module as part of calling the AMD define :
//// [amdModule.js]
define("NamedModule", ["require", "exports"], function(require, exports) {
var C = (function() {
function C() {}
return C;
})();
exports.C = C;
});
Go to TOC
487
typescript
ES6 Modules
TypeScript 1.5 supports ECMAScript 6 (ES6) modules. ES6 modules are effectively TypeScript external mod-
ules with a new syntax: ES6 modules are separately loaded source files that possibly import other modules
and provide a number of externally accessible exports. ES6 modules feature several new export and import
declarations. It is recommended that TypeScript libraries and applications be updated to use the new
syntax, but this is not a requirement. The new ES6 module syntax coexists with TypeScript's original inter-
nal and external module constructs and the constructs can be mixed and matched at will.
Export Declarations
In addition to the existing TypeScript support for decorating declarations with export , module members
can also be exported using separate export declarations, optionally specifying different names for exports
using as clauses.
Import declarations, as well, can optionally use as clauses to specify different local names for the imports.
For example:
As an alternative to individual imports, a namespace import can be used to import an entire module:
Re-exporting
Using from clause a module can copy the exports of a given module to the current module without intro-
ducing local names.
export * can be used to re-export all exports of another module. This is useful for creating modules that
aggregate the exports of several other modules.
Default Export
An export default declaration specifies an expression that becomes the default export of a module:
488
TypeScript 1.5
Bare Import
A "bare import" can be used to import a module only for its side-effects.
import "./polyfills";
For more information about module, please see the ES6 module support spec.
Declarations
A destructuring declaration introduces one or more named variables and initializes them with values ex-
tracted from properties of an object or elements of an array.
For example, the following sample declares variables x, y , and z , and initializes them to
getSomeObject().x , getSomeObject().y and getSomeObject().z respectively:
var { x, y, z } = getSomeObject();
function drawText({ text = "", location: [x, y] = [0, 0], bold = false }) {
// Draw text
}
Assignments
Destructuring patterns can also be used in regular assignment expressions. For instance, swapping two
variables can be written as a single destructuring assignment:
489
typescript
var x = 1;
var y = 2;
[x, y] = [y, x];
namespace keyword
TypeScript used the module keyword to define both "internal modules" and "external modules"; this has
been a bit of confusion for developers new to TypeScript. "Internal modules" are closer to what most people
would call a namespace; likewise, "external modules" in JS speak really just are modules now.
Before:
module Math {
export function add(x, y) { ... }
}
After:
namespace Math {
export function add(x, y) { ... }
}
Const
Block scoped
if (true) {
let a = 4;
// use a
} else {
let a = "string";
// use a
}
490
TypeScript 1.5
for..of support
TypeScript 1.5 adds support to ES6 for..of loops on arrays for ES3/ES5 as well as full support for Iterator in-
terfaces when targeting ES6.
Example
The TypeScript compiler will transpile for..of arrays to idiomatic ES3/ES5 JavaScript when targeting those
versions:
Decorators
A decorator is:
an expression
that evaluates to a function
that takes the target, name, and property descriptor as arguments
and optionally returns a property descriptor to install on the target object
Example
Decorators readonly and enumerable(false) will be applied to the property method before it is in-
stalled on class C . This allows the decorator to change the implementation, and in this case, augment the
descriptor to be writable: false and enumerable: false.
class C {
@readonly
@enumerable(false)
method() { ... }
}
491
typescript
function enumerable(value) {
return function (target, key, descriptor) {
descriptor.enumerable = value;
};
}
Computed properties
Initializing an object with dynamic properties can be a bit of a burden. Take the following example:
Here we need to create a variable to hold on to the neighbor-map so that we can initialize it. With
TypeScript 1.5, we can let the compiler do the heavy lifting:
Usage:
and
492
TypeScript 1.5
As an example, consider the need to escape a string that contains the character ' 𠮷'. In UTF-16/UCS2, '𠮷' is
represented as a surrogate pair, meaning that it's encoded using a pair of 16-bit code units of values,
specifically 0xD842 and 0xDFB7 . Previously this meant that you'd have to escape the codepoint as
"\uD842\uDFB7" . This has the major downside that it’s difficult to discern two independent characters from
a surrogate pair.
With ES6’s codepoint escapes, you can cleanly represent that exact character in strings and template strings
with a single escape: "\u{20bb7}" . TypeScript will emit the string in ES3/ES5 as "\uD842\uDFB7" .
will be emitted as
The new amd-dependency name property allows passing an optional name for an amd-dependency:
Generated JS code:
493
typescript
moduleA
) {
moduleA.callStuff();
});
By invoking tsc with no input files, in which case the compiler searches for the tsconfig.json file starting
in the current directory and continuing up the parent directory chain.
By invoking tsc with no input files and a -project (or just -p) command line option that specifies the path
of a directory containing a tsconfig.json file.
Example
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"sourceMap": true
}
}
Sometimes this is not desirable, for instance inputs FolderA\FolderB\1.ts and FolderA\FolderB\2.ts
would result in output structure mirroring FolderA\FolderB\ . Now if a new file FolderA\3.ts is added to
the input, the output structure will pop out to mirror FolderA\ .
rootDir specifies the input directory to be mirrored in output instead of computing it.
494
TypeScript 1.5
Go to TOC
495
typescript
JSX support
JSX is an embeddable XML-like syntax. It is meant to be transformed into valid JavaScript, but the seman-
tics of that transformation are implementation-specific. JSX came to popularity with the React library but
has since seen other applications. TypeScript 1.6 supports embedding, type checking, and optionally compil-
ing JSX directly into JavaScript.
TypeScript 1.6 introduces a new .tsx file extension. This extension does two things: it enables JSX inside
of TypeScript files, and it makes the new as operator the default way to cast (removing any ambiguity be-
tween JSX expressions and the TypeScript prefix cast operator). For example:
var x = <any>foo;
// is equivalent to:
var x = foo as any;
Using React
To use JSX-support with React you should use the React typings. These typings define the JSX namespace
so that TypeScript can correctly check JSX expressions for React. For example:
interface Props {
name: string;
}
JSX element names and properties are validated against the JSX namespace. Please see the [[JSX]] wiki
page for defining the JSX namespace for your framework.
Output generation
The preserve mode will keep JSX expressions as part of the output to be further consumed by another
transform step. Additionally the output will have a .jsx file extension.
The react mode will emit React.createElement , does not need to go through a JSX transformation
before use, and the output will have a .js file extension.
496
TypeScript 1.6
See the [[JSX]] wiki page for more information on using JSX in TypeScript.
Intersection types
TypeScript 1.6 introduces intersection types, the logical complement of union types. A union type A | B
represents an entity that is either of type A or type B , whereas an intersection type A & B represents an
entity that is both of type A and type B .
Example
interface Person {
name: string;
}
interface A {
a: string;
}
interface B {
b: string;
}
interface C {
c: string;
}
497
typescript
function f() {
if (true) {
interface T {
x: number;
}
let v: T;
v.x = 5;
} else {
interface T {
x: string;
}
let v: T;
v.x = "hello";
}
}
The inferred return type of a function may be a type declared locally within the function. It is not possible
for callers of the function to reference such a local type, but it can of course be matched structurally. For
example:
interface Point {
x: number;
y: number;
}
Local types may reference enclosing type parameters and local class and interfaces may themselves be
generic. For example:
function f3() {
function f<X, Y>(x: X, y: Y) {
class C {
public x = x;
public y = y;
}
return C;
}
let C = f(10, "hello");
let v = new C();
498
TypeScript 1.6
Class expressions
TypeScript 1.6 adds support for ES6 class expressions. In a class expression, the class name is optional
and, if specified, is only in scope in the class expression itself. This is similar to the optional name of a func-
tion expression. It is not possible to refer to the class instance type of a class expression outside the class
expression, but the type can of course be matched structurally. For example:
Extending expressions
TypeScript 1.6 adds support for classes extending arbitrary expression that computes a constructor
function. This means that built-in types can now be extended in class declarations.
The extends clause of a class previously required a type reference to be specified. It now accepts an ex-
pression optionally followed by a type argument list. The type of the expression must be a constructor func-
tion type with at least one construct signature that has the same number of type parameters as the number
of type arguments specified in the extends clause. The return type of the matching construct signature(s)
is the base type from which the class instance type inherits. Effectively, this allows both real classes and
"class-like" expressions to be specified in the extends clause.
Some examples:
class ThingA {
getGreeting() {
return "Hello from A";
}
}
class ThingB {
getGreeting() {
return "Hello from B";
}
}
interface Greeter {
499
typescript
getGreeting(): string;
}
interface GreeterConstructor {
new (): Greeter;
}
Examples
var s: Lazy<string>;
s = "eager";
500
TypeScript 1.6
s = () => "lazy";
Examples
A type can include an index signature to explicitly indicate that excess properties are permitted:
ES6 generators
TypeScript 1.6 adds support for generators when targeting ES6.
A generator function can have a return type annotation, just like a function. The annotation represents the
type of the generator returned by the function. Here is an example:
A generator function with no type annotation can have the type annotation inferred. So in the following
case, the type will be inferred from the yield statements:
function* g() {
for (var i = 0; i < 100; i++) {
yield ""; // infer string
}
yield* otherStringGenerator(); // infer element type of otherStringGenerator
}
501
typescript
An async function is a function or method that has been prefixed with the async modifier. This modifier in-
forms the compiler that function body transposition is required, and that the keyword await should be
treated as a unary expression instead of an identifier. An Async Function must provide a return type annota-
tion that points to a compatible Promise type. Return type inference can only be used if there is a globally
defined, compatible Promise type.
Example
class C {
async m(): Promise<number> {
var i = await p; // suspend execution until 'p' is settled. 'i' has type
"number"
return 1 + i;
}
Nightly builds
While not strictly a language change, nightly builds are now available by installing with the following
command:
502
TypeScript 1.6
interface Foo {
y: string;
}
Examples
503
typescript
{
"compilerOptions": {
"out": "test.js"
},
"exclude": ["node_modules", "test.ts", "utils/t2.ts"]
}
The exclude list does not support wilcards. It must simply be a list of files and/or directories.
Go to TOC
504
TypeScript 1.7
Example
In the following example, each input element will be printed out one at a time with a 400ms delay:
"use strict";
// printDelayed is a 'Promise<void>'
async function printDelayed(elements: string[]) {
for (const element of elements) {
await delay(400);
console.log(element);
}
}
Example
{
"compilerOptions": {
"module": "amd",
"target": "es6"
}
}
505
typescript
this -typing
It is a common pattern to return the current object (i.e. this ) from a method to create fluent-style APIs.
For instance, consider the following BasicCalculator module:
This often opens up very elegant ways of writing code; however, there was a problem for classes that want-
ed to extend from BasicCalculator . Imagine a user wanted to start writing a ScientificCalculator :
public square() {
this.value = this.value ** 2;
return this;
}
public sin() {
this.value = Math.sin(this.value);
506
TypeScript 1.7
return this;
}
}
Because TypeScript used to infer the type BasicCalculator for each method in BasicCalculator that
returned this , the type system would forget that it had ScientificCalculator whenever using a
BasicCalculator method.
For instance:
This is no longer the case - TypeScript now infers this to have a special type called this whenever inside
an instance method of a class. The this type is written as so, and basically means "the type of the left
side of the dot in a method call".
The this type is also useful with intersection types in describing libraries (e.g. Ember.js) that use mixin-
style patterns to describe inheritance:
interface MyType {
extend<T>(other: T): this & T;
}
Example
var x = 2 ** 3;
var y = 10;
y **= 2;
var z = -(4 ** 3);
507
typescript
When an object literal is contextually typed by the implied type of an object binding pattern:
Properties with default values in the object binding pattern become optional in the object literal.
Properties in the object binding pattern that have no match in the object literal are required to have a
default value in the object binding pattern and are automatically added to the object literal type.
Properties in the object literal that have no match in the object binding pattern are an error.
When an array literal is contextually typed by the implied type of an array binding pattern:
Elements in the array binding pattern that have no match in the array literal are required to have a de-
fault value in the array binding pattern and are automatically added to the array literal type.
Example
f2();
f2({}); // Error, x not optional
f2({ x: 1 });
f2({ y: 1 }); // Error, x not optional
f2({ x: 1, y: 1 });
Go to TOC
508
TypeScript 1.8
Example
let x = { a: 1, b: 2, c: 3, d: 4 };
assign(x, { b: 10, d: 20 });
assign(x, { e: 0 }); // Error
cfa
509
typescript
Unreachable code
Statements guaranteed to not be executed at run time are now correctly flagged as unreachable code
errors. For instance, statements following unconditional return , throw , break or continue statements
are considered unreachable. Use allowUnreachableCode to disable unreachable code detection and
reporting.
Example
function f(x) {
if (x) {
return true;
} else {
return false;
}
A more common error that this feature catches is adding a newline after a return statement:
function f() {
return; // Automatic Semicolon Insertion triggered at newline
{
x: "string"; // Error: Unreachable code detected.
}
}
Since JavaScript automatically terminates the return statement at the end of the line, the object literal
becomes a block.
Unused labels
Unused labels are also flagged. Just like unreachable code checks, these are turned on by default; use al‐
lowUnusedLabels to stop reporting these errors.
Example
Implicit returns
Functions with code paths that do not return a value in JS implicitly return undefined . These can now be
flagged by the compiler as implicit returns. The check is turned off by default; use noImplicitReturns to
turn it on.
510
TypeScript 1.8
Example
function f(x) {
// Error: Not all code paths return a value.
if (x) {
return false;
}
Example
switch (x % 2) {
case 0: // Error: Fallthrough case in switch.
console.log("even");
case 1:
console.log("odd");
break;
}
However, in the following example, no error will be reported because the fall-through case is empty:
switch (x % 3) {
case 0:
case 1:
console.log("Acceptable");
break;
case 2:
console.log("This is *two much*!");
break;
}
// Use parameter destructuring and defaults for easy definition of 'props' type
const Greeter = ({ name = "world" }) => <div>Hello, {name}!</div>;
For this feature and simplified props, be sure to be use the latest version of react.d.ts.
511
typescript
Specifically:
You no longer need to either explicitly declare ref and key or extend React.Props
The ref and key properties will appear with correct types on all components
The ref property is correctly disallowed on instances of Stateless Function components
Furthermore, TypeScript also has the notion of global augmentations of the form declare global { } .
This allows modules to augment global types such as Array if necessary.
The name of a module augmentation is resolved using the same set of rules as module specifiers in import
and export declarations. The declarations in a module augmentation are merged with any existing decla-
rations the same way they would if they were declared in the same file.
Neither module augmentations nor global augmentations can add new items to the top level scope - they
can only "patch" existing declarations.
Example
Here map.ts can declare that it will internally patch the Observable type from observable.ts and add
the map method to it.
// observable.ts
export class Observable<T> {
// ...
}
// map.ts
import { Observable } from "./observable";
Observable.prototype.map = /*...*/;
512
TypeScript 1.8
// consumer.ts
import { Observable } from "./observable";
import "./map";
let o: Observable<number>;
o.map((x) => x.toFixed());
Similarly, the global scope can be augmented from modules using a declare global declarations:
Example
declare global {
interface Array<T> {
mapToNumbers(): number[];
}
}
Array.prototype.mapToNumbers = function () {
/* ... */
};
interface AnimationOptions {
deltaX: number;
deltaY: number;
easing: string; // Can be "ease-in", "ease-out", "ease-in-out"
}
However, this is error prone - there is nothing stopping a user from accidentally misspelling one of the valid
easing values:
// No errors
new UIElement().animate({ deltaX: 100, deltaY: 100, easing: "ease-inout" });
With TypeScript 1.8, we've introduced string literal types. These types are written the same way string liter-
als are, but in type positions.
Users can now ensure that the type system will catch such errors. Here's our new AnimationOptions us-
ing string literal types:
interface AnimationOptions {
deltaX: number;
deltaY: number;
easing: "ease-in" | "ease-out" | "ease-in-out";
}
513
typescript
Example
A module name will be computed for each module based on its relative location to rootDir .
Example
// file src/a.ts
import * as B from "./lib/b";
export function createA() {
return B.createB();
}
// file src/lib/b.ts
export function createB() {
return {};
}
514
TypeScript 1.8
Results in:
Setting the new compiler flag allowSyntheticDefaultImports indicates that the module loader performs
some kind of synthetic default import member creation not indicated in the imported .ts or .d.ts. The com-
piler will infer the existence of a default export that has the shape of the entire module itself.
Example
is compiled to:
515
typescript
list.forEach(function (f) {
return console.log(f());
});
And results in
0
1
2
3
4
Example
var a: MyObject[];
for (var x in a) {
// Type of x is implicitly string
var obj = a[x]; // Type of obj is MyObject
}
516
TypeScript 1.8
.js files are now allowed as input to tsc . The TypeScript compiler checks the input .js files for syntax
errors, and emits valid output based on the target and module flags. The output can be combined with
other .ts files as well. Source maps are still generated for .js files just like with .ts files.
The new factory name will be used to call createElement and __spread functions.
Example
Compiled with:
Results in:
"use strict";
var jsxFactory_1 = require("jsxFactory");
var div = jsxFactory_1.jsxFactory.createElement("div", null, "Hello JSX!");
this is T is now valid return type annotation for methods in classes and interfaces. When used in a type
narowing position (e.g. if statement), the type of the call expression target object would be narrowed to
T.
Example
class FileSystemObject {
isFile(): this is File {
return this instanceof File;
}
isDirectory(): this is Directory {
return this instanceof Directory;
}
isNetworked(): this is Networked & this {
return this.networked;
}
constructor(public path: string, private networked: boolean) {}
}
517
typescript
}
}
class Directory extends FileSystemObject {
children: FileSystemObject[];
}
interface Networked {
host: string;
}
Microsoft.TypeScript.Compiler
Microsoft.TypeScript.MSBuild
Also, a nightly NuGet package to match the nightly npm package is available on myget:
TypeScript-Preview
By just passing the pretty command line option, TypeScript gives more colorful output with context about
where things are going wrong.
518
TypeScript 1.8
jsx
The classification can be further customized by changing the font and color settings for the VB XML color
and font settings through Tools -> Options -> Environment -> Fonts and Colors page.
The old behavior still remains the same if given a directory - the compiler will try to find a file in the directo-
ry named tsconfig.json .
519
typescript
{
"compilerOptions": {
"target": "ES2015", // running on node v5, yaay!
"sourceMap": true // makes debugging easier
},
/*
* Excluded files
*/
"exclude": ["file.d.ts"]
}
As an example, on many Unix-like systems, the standard output stream is accessible by the file /dev/std‐
out .
As an example, we can pipe our emitted JavaScript into a pretty printer like pretty-js:
520
TypeScript 1.8
We also disable the project properties page when you add a tsconfig.json file. This means that all con-
figuration changes have to be made in the tsconfig.json file itself.
A couple of limitations
If you add a tsconfig.json file, TypeScript files that are not considered part of that context are not
compiled.
Apache Cordova Apps still have the existing limitation of a single tsconfig.json file, which must be in
either the root or the scripts folder.
There is no template for tsconfig.json in most project types.
Go to TOC
521
typescript
The type checker previously considered null and undefined assignable to anything. Effectively, null
and undefined were valid values of every type and it wasn't possible to specifically exclude them (and
therefore not possible to detect erroneous use of them).
--strictNullChecks
strictNullChecks switches to a new strict null checking mode.
In strict null checking mode, the null and undefined values are not in the domain of every type and are
only assignable to themselves and any (the one exception being that undefined is also assignable to
void ). So, whereas T and T | undefined are considered synonymous in regular type checking mode
(because undefined is considered a subtype of any T ), they are different types in strict type checking
mode, and only T | undefined permits undefined values. The same is true for the relationship of T to
T | null .
Example
Assigned-before-use checking
In strict null checking mode the compiler requires every reference to a local variable of a type that doesn't
include undefined to be preceded by an assignment to that variable in every possible preceding code
path.
522
TypeScript 2.0
Example
The compiler checks that variables are definitely assigned by performing control flow based type analysis.
See later for further details on this topic.
Example
Non-null and non-undefined type guards may use the == , != , === , or !== operator to compare to null
or undefined , as in x != null or x === undefined . The effects on subject variable types accurately re-
flect JavaScript semantics (e.g. double-equals operators check for both values no matter which one is speci-
fied whereas triple-equals only checks for the specified value).
523
typescript
Example
interface Options {
location?: {
x?: number;
y?: number;
};
}
Type guards for dotted names also work with user defined type guard functions and the typeof and in‐
stanceof operators and do not depend on the strictNullChecks compiler option.
A type guard for a dotted name has no effect following an assignment to any part of the dotted name. For
example, a type guard for x.y.z will have no effect following an assignment to x , x.y , or x.y.z .
Expression operators
Expression operators permit operand types to include null and/or undefined but always produce values
of non-null and non-undefined types.
The && operator adds null and/or undefined to the type of the right operand depending on which are
present in the type of the left operand, and the || operator removes both null and undefined from the
type of the left operand in the resulting union type.
Type widening
The null and undefined types are not widened to any in strict null checking mode.
524
TypeScript 2.0
In regular type checking mode the inferred type of z is any because of widening, but in strict null check-
ing mode the inferred type of z is null (and therefore, absent a type annotation, null is the only possi-
ble value for z ).
Compatibility
The new features are designed such that they can be used in both strict null checking mode and regular
type checking mode. In particular, the null and undefined types are automatically erased from union
types in regular type checking mode (because they are subtypes of all other types), and the ! non-null as-
sertion expression operator is permitted but has no effect in regular type checking mode. Thus, declaration
files that are updated to use null- and undefined-aware types can still be used in regular type checking
mode for backwards compatibility.
In practical terms, strict null checking mode requires that all files in a compilation are null- and undefined-
aware.
Example
525
typescript
x = 1;
x; // type of x is number here
}
x; // type of x is number | boolean here
}
Control flow based type analysis is particularly relevant in strictNullChecks mode because nullable types
are represented using union types:
Furthermore, in strictNullChecks mode, control flow based type analysis includes definite assignment
analysis for local variables of types that don't permit the value undefined .
Example
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
526
TypeScript 2.0
interface Circle {
kind: "circle";
radius: number;
}
A discriminant property type guard is an expression of the form x.p == v , x.p === v , x.p != v , or
x.p !== v , where p and v are a property and an expression of a string literal type or a union of string
literal types. The discriminant property type guard narrows the type of x to those constituent types of x
that have a discriminant property p with one of the possible values of v .
Note that we currently only support discriminant properties of string literal types. We intend to later add
support for boolean and numeric literal types.
527
typescript
In a function expression or arrow function with no return type annotation, if the function has no return
statements, or only return statements with expressions of type never , and if the end point of the
function is not reachable (as determined by control flow analysis), the inferred return type for the func-
tion is never .
In a function with an explicit never return type annotation, all return statements (if any) must have
expressions of type never and the end point of the function must not be reachable.
Because never is a subtype of every type, it is always omitted from union types and it is ignored in func-
tion return type inference as long as there are other types being returned.
Because never is assignable to every type, a function returning never can be used when a callback re-
turning a more specific type is required:
528
TypeScript 2.0
Read-only properties may have initializers and may be assigned to in constructors within the same class de-
claration, but otherwise assignments to read-only properties are disallowed.
A property declared with a get accessor and no set accessor is considered read-only.
In the type of an enum object, enum members are considered read-only properties.
In the type of a module object, exported const variables are considered read-only properties.
An entity declared in an import statement is considered read-only.
An entity accessed through an ES2015 namespace import is considered read-only (e.g. foo.x is read-
only when foo is declared as import * as foo from "foo" ).
Example
interface Point {
readonly x: number;
readonly y: number;
}
var p2 = { x: 1, y: 1 };
var p3: Point = p2; // Ok, read-only alias for p2
p3.x = 5; // Error, p3.x is read-only
p2.x = 5; // Ok, but also changes p3.x because of aliasing
class Foo {
readonly a = 1;
readonly b: string;
constructor() {
this.b = "hello"; // Assignment permitted in constructor
}
}
529
typescript
By default the type of this inside a function is any . Starting with TypeScript 2.0, you can provide an ex-
plicit this parameter. this parameters are fake parameters that come first in the parameter list of a
function:
Example
interface UIElement {
addClickListener(onclick: (this: void, e: Event) => void): void;
}
this: void means that addClickListener expects onclick to be a function that does not require a
this type.
class Handler {
info: string;
onClickBad(this: Handler, e: Event) {
// oops, used this here. using this callback would crash at runtime
this.info = e.message;
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickBad); // error!
--noImplicitThis
A new flag is also added in TypeScript 2.0 to flag all uses of this in functions without an explicit type
annotation.
Glob-like file patterns are supported two properties include and exclude .
530
TypeScript 2.0
Example
{
"compilerOptions": {
"module": "commonjs",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"outFile": "../../built/local/tsc.js",
"sourceMap": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "**/*.spec.ts"]
}
If a segment of a glob pattern includes only * or .* , then only files with supported extensions are includ-
ed (e.g. .ts , .tsx , and .d.ts by default with .js and .jsx if allowJs is set to true).
If the files and include are both left unspecified, the compiler defaults to including all TypeScript
( .ts , .d.ts and .tsx ) files in the containing directory and subdirectories except those excluded using
the exclude property. JS files ( .js and .jsx ) are also included if allowJs is set to true.
If the files or include properties are specified, the compiler will instead include the union of the files in-
cluded by those two properties. Files in the directory specified using the outDir compiler option are always
excluded unless explicitly included via the files property (even when the exclude property is specified).
Files included using include can be filtered using the exclude property. However, files included explicitly
using the files property are always included regardless of exclude . The exclude property defaults to
excluding the node_modules , bower_components , and jspm_packages directories when not specified.
Base URL
Using a baseUrl is a common practice in applications using AMD module loaders where modules are "de-
ployed" to a single folder at run-time. All module imports with non-relative names are assumed to be rela-
tive to the baseUrl .
531
typescript
Example
{
"compilerOptions": {
"baseUrl": "./modules"
}
}
Path mapping
Sometimes modules are not directly located under baseUrl. Loaders use a mapping configuration to map
module names to files at run-time, see RequireJs documentation and SystemJS documentation.
The TypeScript compiler supports the declaration of such mappings using paths property in tsconfig.j‐
son files.
Example
{
"compilerOptions": {
"baseUrl": "./node_modules",
"paths": {
"jquery": ["jquery/dist/jquery.slim.min"]
}
}
Using paths also allow for more sophisticated mappings including multiple fall back locations. Consider a
project configuration where only some modules are available in one location, and the rest are in another.
Example
src
└── views
└── view1.ts (imports './template1')
└── view2.ts
generated
532
TypeScript 2.0
└── templates
└── views
└── template1.ts (imports './view2')
A build step will copy the files in /src/views and /generated/templates/views to the same directory in
the output. At run-time, a view can expect its template to exist next to it, and thus should import it using a
relative name as "./template" .
rootDirs specify a list of roots whose contents are expected to merge at run-time. So following our exam-
ple, the tsconfig.json file should look like:
{
"compilerOptions": {
"rootDirs": ["src/views", "generated/templates/views"]
}
}
tsc --traceResolution
declarations.d.ts
All imports from a shorthand module will have the any type.
TypeScript 2.0 supports the use of the wildcard character ( * ) to declare a "family" of module names; this
way, a declaration is only required once for an extension, and not for every resource.
Example
533
typescript
Wildcard module names can be even more useful when migrating from an un-typed code base. Combined
with Shorthand ambient module declarations, a set of modules can be easily declared as any .
Example
All imports to any module under myLibrary would be considered to have the type any by the compiler;
thus, shutting down any checking on the shapes or types of these modules.
For example:
math-lib.d.ts
It can also be used as a global variable, but only inside of a script. (A script is a file with no imports or
exports.)
mathLib.isPrime(2);
534
TypeScript 2.0
Example
class Bar {
a: number;
b?: number;
f() {
return 1;
}
g?(): number; // Body of optional method can be omitted
h?() {
return 2;
}
}
When compiled in strictNullChecks mode, optional properties and methods automatically have unde‐
fined included in their type. Thus, the b property above is of type number | undefined and the g
method above is of type (() => number) | undefined . Type guards can be used to strip away the unde‐
fined part of the type:
Example
class Singleton {
private static instance: Singleton;
private constructor() {}
static getInstance() {
if (!Singleton.instance) {
Singleton.instance = new Singleton();
}
return Singleton.instance;
}
}
535
typescript
Example
value = 1;
}
const headers = {
"Content-Type": "application/x-www-form-urlencoded",
};
dom
webworker
es5
536
TypeScript 2.0
es6 / es2015
es2015.core
es2015.collection
es2015.iterable
es2015.promise
es2015.proxy
es2015.reflect
es2015.generator
es2015.symbol
es2015.symbol.wellknown
es2016
es2016.array.include
es2017
es2017.object
es2017.sharedmemory
scripthost
Example
"compilerOptions": {
"lib": ["es5", "es2015.promise"]
}
Example
Parameters declaration with names starting with _ are exempt from the unused parameter checking. e.g.:
function returnNull(_a) {
// OK
return null;
}
537
typescript
With TypeScript 2.0, the compiler will look up definition of "moduleA.js" in ./moduleA.ts or ./module‐
A.d.t .
Example
function foo(
bar: Bar,
baz: Baz // trailing commas are OK in parameter lists
) {
// Implementation...
}
foo(
bar,
baz // and in argument lists
);
New --skipLibCheck
TypeScript 2.0 adds a new skipLibCheck compiler option that causes type checking of declaration files
(files with extension .d.ts ) to be skipped. When a program includes large declaration files, the compiler
spends a lot of time type checking declarations that are already known to not contain errors, and compile
times may be significantly shortened by skipping declaration file type checks.
Since declarations in one file can affect type checking in other files, some errors may not be detected when
skipLibCheck is specified. For example, if a non-declaration file augments a type declared in a declaration
file, errors may result that are only reported when the declaration file is checked. However, in practice such
situations are rare.
538
TypeScript 2.0
TypeScript 2.0 relaxes this constraint and allows duplicate identifiers across blocks, as long as they have
identical types.
Example
interface Error {
stack?: string;
}
interface Error {
code?: string;
path?: string;
stack?: string; // OK
}
New --declarationDir
declarationDir allows for generating declaration files in a different location than JavaScript files.
Go to TOC
539
typescript
Enter Index Type Query or keyof ; An indexed type query keyof T yields the type of permitted property
names for T . A keyof T type is considered a subtype of string .
Example
interface Person {
name: string;
age: number;
location: string;
}
The dual of this is indexed access types, also called lookup types. Syntactically, they look exactly like an
element access, but are written as types:
Example
You can use this pattern with other parts of the type system to get type-safe lookups.
Mapped Types
One common task is to take an existing type and make each of its properties entirely optional. Let's say we
have a Person :
540
TypeScript 2.1
interface Person {
name: string;
age: number;
location: string;
}
interface PartialPerson {
name?: string;
age?: number;
location?: string;
}
with Mapped types, PartialPerson can be written as a generalized transformation on the type Person
as:
type Partial<T> = {
[P in keyof T]?: T[P];
};
Mapped types are produced by taking a union of literal types, and computing a set of properties for a new
object type. They're like list comprehensions in Python, but instead of producing new elements in a list,
they produce new properties in a type.
In addition to Partial , Mapped Types can express many useful transformations on types:
// Same property names, but make the value a promise instead of a concrete one
type Deferred<T> = {
[P in keyof T]: Promise<T[P]>;
};
Because of that, they are now included by default in the standard library.
We're also including two other utility types as well: Record and Pick .
541
typescript
Similar to array spread, spreading an object can be handy to get a shallow copy:
Similarly, you can merge several different objects. In the following example, merged will have properties
from foo , bar , and baz .
You can also override existing properties and add new ones:
The order of specifying spread operations determines what properties end up in the resulting object; prop-
erties in later spreads "win out" over previously created properties.
Object rests are the dual of object spreads, in that they can extract any extra properties that don't get
picked up when destructuring an element:
let obj = { x: 1, y: 1, z: 1 };
let { z, ...obj1 } = obj;
obj1; // {x: number, y:number};
542
TypeScript 2.1
Note: first, we need to make sure our run-time has an ECMAScript-compliant Promise available glob-
ally. That might involve grabbing a polyfill for Promise , or relying on one that you might have in the
run-time that you're targeting. We also need to make sure that TypeScript knows Promise exists by
setting your lib option to something like "dom", "es2015" or "dom", "es2015.promise", "es5"
Example
tsconfig.json
{
"compilerOptions": {
"lib": ["dom", "es2015.promise", "es5"]
}
}
dramaticWelcome.ts
console.log("World!");
}
dramaticWelcome();
Compiling and running the output should result in the correct behavior on an ES3/ES5 engine.
The two options left more to be desired; bundling the helpers in every file was a pain point for customers
trying to keep their package size small. And not including helpers, meant customers had to maintain their
own helpers library.
543
typescript
TypeScript 2.1 allows for including these files in your project once in a separate module, and the compiler
will emit imports to them as needed.
So given the following input, the resulting .js file will include an import to tslib and use the __assign
helper from it instead of inlining it.
"use strict";
var tslib_1 = require("tslib");
exports.o = { a: 1, name: "o" };
exports.copy = tslib_1.__assign({}, exports.o);
Untyped imports
TypeScript has traditionally been overly strict about how you can import modules. This was to avoid typos
and prevent users from using modules incorrectly.
However, a lot of the time, you might just want to import an existing module that may not have its own
.d.ts file. Previously this was an error. Starting with TypeScript 2.1 this is now much easier.
With TypeScript 2.1, you can import a JavaScript module without needing a type declaration. A type decla-
ration (such as declare module "foo" { ... } or node_modules/@types/foo ) still takes priority if it
exists.
An import to a module with no declaration file will still be flagged as an error under noImplicitAny .
Example
Using target --target ES2016 will instruct the compiler not to transform ES2016-specific features, e.g.
** operator.
544
TypeScript 2.1
Similarly, --target ES2017 will instruct the compiler not to transform ES2017-specific features like
async / await .
With TypeScript 2.1, instead of just choosing any , TypeScript will infer types based on what you end up as-
signing later on.
Example
let x;
// After that last assignment, TypeScript 2.1 knows that 'x' has type '() =>
number'.
let y = x();
// Thanks to that, it will now tell you that you can't add a number to a function!
console.log(x + y);
// ~~~~~
// Error! Operator '+' cannot be applied to types '() => number' and 'number'.
The same sort of tracking is now also done for empty arrays.
A variable declared with no type annotation and an initial value of [] is considered an implicit any[] vari-
able. However, each subsequent x.push(value) , x.unshift(value) or x[n] = value operation evolves
the type of the variable in accordance with what elements are added to it.
function f1() {
let x = [];
x.push(5);
x[1] = "hello";
x.unshift(true);
return x; // (string | number | boolean)[]
}
545
typescript
function f2() {
let x = null;
if (cond()) {
x = [];
while (cond()) {
x.push("hello");
}
}
return x; // string[] | null
}
Example
function f3() {
let x = []; // Error: Variable 'x' implicitly has type 'any[]' in some locations
where its type cannot be determined.
x.push(5);
function g() {
x; // Error: Variable 'x' implicitly has an 'any[]' type.
}
}
The type inferred for a const variable or readonly property without a type annotation is the type of the
literal initializer. The type inferred for a let variable, var variable, parameter, or non- readonly property
with an initializer and no type annotation is the widened literal type of the initializer. Where the widened
type for a string literal type is string , number for numeric literal types, boolean for true or false
and the containing enum for enum literal types.
Example
const c1 = 1; // Type 1
const c2 = c1; // Type 1
const c3 = "abc"; // Type "abc"
const c4 = true; // Type true
const c5 = cond ? 1 : "abc"; // Type 1 | "abc"
546
TypeScript 2.1
Literal type widening can be controlled through explicit type annotations. Specifically, when an expression of
a literal type is inferred for a const location without a type annotation, that const variable gets a widening
literal type inferred. But when a const location has an explicit literal type annotation, the const variable
gets a non-widening literal type.
Example
Example
class Base {
x: number;
constructor() {
// return a new object other than `this`
return {
x: 1
};
}
}
Generates:
547
typescript
This change entails a break in the behavior of extending built-in classes like Error , Array , Map ,
etc.. Please see the extending built-ins breaking change documentation for more details.
Configuration inheritance
Often a project has multiple output targets, e.g. ES5 and ES2015 , debug and production or CommonJS
and System ; Just a few configuration options change between these two targets, and maintaining multiple
tsconfig.json files can be a hassle.
Example
configs/base.json :
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true
}
}
tsconfig.json :
{
"extends": "./configs/base",
"files": ["main.ts", "supplemental.ts"]
}
tsconfig.nostrictnull.json :
{
"extends": "./tsconfig",
"compilerOptions": {
"strictNullChecks": false
}
}
New --alwaysStrict
Invoking the compiler with alwaysStrict causes:
548
TypeScript 2.1
Modules are parsed automatically in strict mode. The new flag is recommended for non-module code.
Go to TOC
549
typescript
A mixin constructor type refers to a type that has a single construct signature with a single rest argu-
ment of type any[] and an object-like return type. For example, given an object-like type X , new
(...args: any[]) => X is a mixin constructor type with an instance type X .
A mixin class is a class declaration or expression that extends an expression of a type parameter type.
The following rules apply to mixin class declarations:
The type parameter type of the extends expression must be constrained to a mixin constructor type.
The constructor of a mixin class (if any) must have a single rest parameter of type any[] and must use
the spread operator to pass those parameters as arguments in a super(...args) call.
Given an expression Base of a parametric type T with a constraint X , a mixin class class C extends
Base {...} is processed as if Base had type X and the resulting type is the intersection typeof C & T .
In other words, a mixin class is represented as an intersection between the mixin class constructor type and
the parametric base class constructor type.
When obtaining the construct signatures of an intersection type that contains mixin constructor types, the
mixin construct signatures are discarded and their instance types are mixed into the return types of the oth-
er construct signatures in the intersection type. For example, the intersection type { new(...args:
any[]) => A } & { new(s: string) => B } has a single construct signature new(s: string) => A &
B.
class Point {
constructor(public x: number, public y: number) {}
}
class Person {
constructor(public name: string) {}
}
550
TypeScript 2.2
Mixin classes can constrain the types of classes they can mix into by specifying a construct signature return
type in the constraint for the type parameter. For example, the following WithLocation function imple-
ments a subclass factory that adds a getLocation method to any class that satisfies the Point interface
(i.e. that has x and y properties of type number ).
interface Point {
x: number;
y: number;
}
object type
TypeScript did not have a type that represents the non-primitive type, i.e. any thing that is not number ,
string , boolean , symbol , null , or undefined . Enter the new object type.
With object type, APIs like Object.create can be better represented. For example:
create(42); // Error
create("string"); // Error
create(false); // Error
create(undefined); // Error
551
typescript
Example
new.target also comes in handy for writing constructable functions, for example:
function f() {
if (new.target) {
/* called via 'new' */
}
}
function f() {
var _newTarget = this && this instanceof f ? this.constructor : void 0;
if (_newTarget) {
/* called via 'new' */
}
}
If either operand of a + operator is nullable, and neither operand is of type any or string .
If either operand of a - , * , ** , / , % , << , >> , >>> , & , | , or ^ operator is nullable.
If either operand of a < , > , <= , >= , or in operator is nullable.
If the right operand of an instanceof operator is nullable.
552
TypeScript 2.2
An operand is considered nullable if the type of the operand is null or undefined or a union type that in-
cludes null or undefined . Note that the union type case only only occurs in strictNullChecks mode
because null and undefined disappear from unions in classic type checking mode.
interface StringMap<T> {
[x: string]: T;
}
map["prop1"] = 1;
map.prop2 = 2;
This only apply to types with an explicit string index signature. It is still an error to access unknown proper-
ties on a type using . notation.
Example
let x: TodoListProps;
553
typescript
Go to TOC
554
TypeScript 2.3
Iterators
ES2015 introduced Iterator , which is an object that exposes three methods, next , return , and
throw , as per the following interface:
interface Iterator<T> {
next(value?: any): IteratorResult<T>;
return?(value?: any): IteratorResult<T>;
throw?(e?: any): IteratorResult<T>;
}
This kind of iterator is useful for iterating over synchronously available values, such as the elements of an
Array or the keys of a Map. An object that supports iteration is said to be "iterable" if it has a
Symbol.iterator method that returns an Iterator object.
The Iterator protocol also defines the target of some of the ES2015 features like for..of and spread oper-
ator and the array rest in destructuring assignmnets.
Generators
ES2015 also introduced "Generators", which are functions that can be used to yield partial computation re-
sults via the Iterator interface and the yield keyword. Generators can also internally delegate calls to
another iterable through yield * . For example:
function* f() {
yield 1;
yield* [2, 3];
}
New --downlevelIteration
Previously generators were only supported if the target is ES6/ES2015 or later. Moreover, constructs that
operate on the Iterator protocol, e.g. for..of were only supported if they operate on arrays for targets
below ES6/ES2015.
TypeScript 2.3 adds full support for generators and the Iterator protocol for ES3 and ES5 targets with
downlevelIteration flag.
With downlevelIteration , the compiler uses new type check and emit behavior that attempts to call a
[Symbol.iterator]() method on the iterated object if it is found, and creates a synthetic array iterator
over the object if it is not.
Please note that this requires a native Symbol.iterator or Symbol.iterator shim at runtime for
any non-array values.
555
typescript
for..of statements, Array Destructuring, and Spread elements in Array, Call, and New expressions sup-
port Symbol.iterator in ES5/E3 if available when using downlevelIteration , but can be used on an
Array even if it does not define Symbol.iterator at run time or design time.
Async Iteration
TypeScript 2.3 adds support for the async iterators and generators as described by the current TC39
proposal.
Async iterators
The Async Iteration introduces an AsyncIterator , which is similar to Iterator . The difference lies in the
fact that the next , return , and throw methods of an AsyncIterator return a Promise for the itera-
tion result, rather than the result itself. This allows the caller to enlist in an asynchronous notification for the
time at which the AsyncIterator has advanced to the point of yielding a value. An AsyncIterator has
the following shape:
interface AsyncIterator<T> {
next(value?: any): Promise<IteratorResult<T>>;
return?(value?: any): Promise<IteratorResult<T>>;
throw?(e?: any): Promise<IteratorResult<T>>;
}
An object that supports async iteration is said to be "iterable" if it has a Symbol.asyncIterator method
that returns an AsyncIterator object.
Async Generators
The Async Iteration proposal introduces "Async Generators", which are async functions that also can be
used to yield partial computation results. Async Generators can also delegate calls via yield* to either an
iterable or async iterable:
As with Generators, Async Generators can only be function declarations, function expressions, or methods
of classes or object literals. Arrow functions cannot be Async Generators. Async Generators require a valid,
global Promise implementation (either native or an ES2015-compatible polyfill), in addition to a valid
Symbol.asyncIterator reference (either a native symbol or a shim).
Finally, ES2015 introduced the for..of statement as a means of iterating over an iterable. Similarly, the
Async Iteration proposal introduces the for..await..of statement to iterate over an async iterable:
556
TypeScript 2.3
The for..await..of statement is only legal within an Async Function or Async Generator.
Caveats
Keep in mind that our support for async iterators relies on support for Symbol.asyncIterator to exist
at runtime. You may need to polyfill Symbol.asyncIterator , which for simple purposes can be as sim-
ple as: (Symbol as any).asyncIterator = Symbol.asyncIterator ||
Symbol.for("Symbol.asyncIterator");
You also need to include esnext in your lib option, to get the AsyncIterator declaration if you do
not already have it.
Finally, if your target is ES5 or ES3, you'll also need to set the --downlevelIterators flag.
Example
Consider a function that creates a new HTMLElement , calling it with no arguments generates a Div ; you
can optionally pass a list of children as well. Previously you would have to define it as:
557
typescript
A class or interface declaration that merges with an existing class or interface declaration may introduce
a new type parameter as long as it specifies a default.
The new strict compiler option represents the recommended setting of a number of type checking op-
tions. Specifically, specifying strict corresponds to specifying all of the following options (and may in the
future include more options):
strictNullChecks
noImplicitAny
noImplicitThis
alwaysStrict
In exact terms, the strict option sets the default value for the compiler options listed above. This means
it is still possible to individually control the options. For example,
has the effect of turning on all strict options except the noImplicitThis option. Using this scheme it is
possible to express configurations consisting of all strict options except some explicitly listed options. In
other words, it is now possible to default to the highest level of type safety but opt out of certain checks.
Starting with TypeScript 2.3, the default tsconfig.json generated by tsc --init includes a "strict":
true setting in the "compilerOptions" section. Thus, new projects started with tsc --init will by de-
fault have the highest level of type safety enabled.
558
TypeScript 2.3
You can skip checking some files by adding // @ts-nocheck comment to them; conversely you can choose
to check only a few .js files by adding // @ts-check comment to them without setting checkJs . You
can also ignore errors on specific lines by adding // @ts-ignore on the preceding line.
.js files are still checked to ensure that they only include standard ECMAScript features; type annotations
are only allowed in .ts files and are flagged as errors in .js files. JSDoc comments can be used to add
some type information to your JavaScript code, see JSDoc Support documentation for more details about
the supported JSDoc constructs.
Go to TOC
559
typescript
This means that you can conditionally and lazily import other modules and libraries. For example, here's an
async function that only imports a utility library when it's needed:
Many bundlers have support for automatically splitting output bundles based on these import expressions,
so consider using this new feature with the esnext module target.
String Enums
TypeScript 2.4 now allows enum members to contain string initializers.
enum Colors {
Red = "RED",
Green = "GREEN",
Blue = "BLUE"
}
The caveat is that string-initialized enums can't be reverse-mapped to get the original enum member name.
In other words, you can't write Colors["RED"] to get the string "Red" .
function arrayMap<T, U>(f: (x: T) => U): (a: T[]) => U[] {
return a => a.map(f);
}
560
TypeScript 2.4
y would have the type any . This meant the program would type-check, but you could technically do any-
thing with y , such as the following:
In TypeScript 2.4, the function on the right side implicitly gains type parameters, and y is inferred to have
the type of that type-parameter.
If you use y in a way that the type parameter's constraint doesn't support, you'll correctly get an error. In
this case, the constraint of T was (implicitly) {} , so the last example will appropriately fail.
function f(a: A, b: B) {
a = b; // Error
b = a; // Ok
}
TypeScript 2.4 introduces tightens this up when relating two callback types. For example:
interface Mappable<T> {
map<U>(f: (x: T) => U): Mappable<U>;
}
a = b;
b = a;
561
typescript
Prior to TypeScript 2.4, this example would succeed. When relating the types of map , TypeScript would
bidirectionally relate their parameters (i.e. the type of f ). When relating each f , TypeScript would also
bidirectionally relate the type of those parameters.
When relating the type of map in TS 2.4, the language will check whether each parameter is a callback
type, and if so, it will ensure that those parameters are checked in a contravariant manner with respect to
the current relation.
In other words, TypeScript now catches the above bug, which may be a breaking change for some users,
but will largely be helpful.
interface Options {
data?: string;
timeout?: number;
maxRetries?: number;
}
In TypeScript 2.4, it's now an error to assign anything to a weak type when there's no overlap in properties.
For example:
const opts = {
payload: "hello world!",
retryOnFail: true
};
// Error!
sendMessage(opts);
// No overlap between the type of 'opts' and 'Options' itself.
// Maybe we meant to use 'data'/'maxRetries' instead of 'payload'/'retryOnFail'.
You can think of this as TypeScript "toughening up" the weak guarantees of these types to catch what would
otherwise be silent bugs.
Since this is a breaking change, you may need to know about the workarounds which are the same as those
for strict object literal checks:
Go to TOC
562
TypeScript 2.5
As a nice bonus, this can also reduce the memory and runtime footprint of the compiler and language ser-
vice by avoiding loading .d.ts files from duplicate packages.
563
typescript
In this mode, references to modules and packages (e.g. import s and /// <reference type="..." />
directives) are all resolved relative to the location of the symbolic link file, rather than relative to the path
that the symbolic link resolves to. For a more concrete example, we'll defer to the documentation on the
Node.js website.
Go to TOC
564
TypeScript 2.6
Under strictFunctionTypes function type parameter positions are checked contravariantly instead of bi-
variantly. For some background on what variance means for function types check out What are covariance
and contravariance?.
The stricter checking applies to all function types, except those originating in method or constructor decla-
rations. Methods are excluded specifically to ensure generic classes and interfaces (such as Array<T> ) con-
tinue to mostly relate covariantly.
Consider the following example in which Animal is the supertype of Dog and Cat :
The first assignment is permitted in default type checking mode, but flagged as an error in strict function
types mode. Intuitively, the default mode permits the assignment because it is possibly sound, whereas
strict function types mode makes it an error because it isn't provably sound. In either mode the third as-
signment is an error because it is never sound.
Another way to describe the example is that the type (x: T) => void is bivariant (i.e. covariant or con-
travariant) for T in default type checking mode, but contravariant for T in strict function types mode.
Example
interface Comparer<T> {
compare: (a: T, b: T) => number;
}
The first assignment is now an error. Effectively, T is contravariant in Comparer<T> because it is used only
in function type parameter positions.
By the way, note that whereas some languages (e.g. C# and Scala) require variance annotations ( out / in
or + / - ), variance emerges naturally from the actual use of a type parameter within a generic type due to
TypeScript's structural type system.
565
typescript
Note
Under strictFunctionTypes the first assignment is still permitted if compare was declared as a method.
Effectively, T is bivariant in Comparer<T> because it is used only in method parameter positions.
interface Comparer<T> {
compare(a: T, b: T): number;
}
Above, all inferences for T originate in contravariant positions, and we therefore infer the best common
subtype for T . This contrasts with inferences from covariant positions, where we infer the best common
supertype.
Example
566
TypeScript 2.6
"use strict";
var __makeTemplateObject =
(this && this.__makeTemplateObject) ||
function(cooked, raw) {
if (Object.defineProperty) {
Object.defineProperty(cooked, "raw", { value: raw });
} else {
cooked.raw = raw;
}
return cooked;
};
function id(x) {
return x;
}
var _a;
function templateObjectFactory() {
return id(
_a || (_a = __makeTemplateObject(["hello world"], ["hello world"]))
);
}
Note: This change brings a new emit helper, __makeTemplateObject ; if you are using importHelper
s with tslib , an updated to version 1.8 or later.
Example
c:\ts>tsc --v
Version 2.6.0-dev.20171003
../test/a.ts(1,5): error TS2322: Тип ""string"" не может быть назначен для типа
"number".
567
typescript
例: tsc hello.ts
tsc --outFile file.js file.ts
tsc @args.txt
オプション:
-h, --help このメッセージを表示します。
--all コンパイラ オプションをすべて表示します。
-v, --version コンパイラのバージョンを表示します。
--init TypeScript プロジェクトを初期化して、
tsconfig.json ファイルを作成します。
-p ファイルまたはディレクトリ, --project ファイルまたはディレクトリ 構成ファイルか、
'tsconfig.json' を含むフォルダーにパスが指定されたプロジェクトをコ
ンパイルします。
--pretty 色とコンテキストを使用してエラーとメッセージに
スタイルを適用します (試験的)。
-w, --watch 入力ファイルを監視します。
-t バージョン, --target バージョン ECMAScript のターゲット バージョンを
指定します: 'ES3' (既定)、'ES5'、'ES2015'、'ES2016'、'ES2017'、'ES
NEXT' 。
-m 種類, --module 種類 モジュール コード生成を指定します: 'none'、
'commonjs' 、 'amd' 、 'system' 、 'umd' 、 'es2015' 、 'ESNext' 。
--lib コンパイルに含めるライブラリ ファイルを指定しま
す:
'es5' 'es6' 'es2015' 'es7' 'es2016'
'es2017' 'esnext' 'dom' 'dom.iterable' 'webworker' 'scripthost' 'es201
5.core' 'es2015.collection' 'es2015.generator' 'es2015.iterable' 'es2015.promise'
'es2015.proxy' 'es2015.reflect' 'es2015.symbol' 'es2015.symbol.wellkno
wn' 'es2016.array.include' 'es2017.object' 'es2017.sharedmemory' 'es2017.string'
'es2017.intl' 'esnext.asynciterable'
--allowJs ファイルのコンパイルを許可します。
javascript
--jsx 種類 JSX コード生成を指定します 、
: 'preserve'
、
'react-native' 'react' 。
-d, --declaration 対応する ファイルを生成します。
'.d.ts'
--sourceMap 対応する ファイルを生成します。
'.map'
--outFile ファイル 出力を連結して つのファイルを生成します。
1
--outDir ディレクトリ ディレクトリへ出力構造をリダイレクトしま
す。
--removeComments コメントを出力しないでください。
--noEmit 出力しないでください。
--strict strict 型チェックのオプションをすべて有効にし
ます。
--noImplicitAny 暗黙的な 'any' 型を含む式と宣言に関するエラー
を発生させます。
--strictNullChecks 厳格な null チェックを有効にします。
--noImplicitThis 暗黙的な 'any' 型を持つ 'this' 式でエラーが
発生します。
--alwaysStrict 厳格モードで解析してソース ファイルごとに "use
strict" を生成します。
--noUnusedLocals 使用されていないローカルに関するエラーを報告し
ます。
--noUnusedParameters 使用されていないパラメーターに関するエラーを報
告します。
--noImplicitReturns 関数の一部のコード パスが値を返さない場合にエ
ラーを報告します。
--noFallthroughCasesInSwitch switch ステートメントに case のフォールスル
ーがある場合にエラーを報告します。
--types コンパイルに含む型宣言ファイル。
@< ファイル>
568
TypeScript 2.6
Example
if (false) {
// @ts-ignore: Unreachable code error
console.log("hello");
}
A // @ts-ignore comment suppresses all errors that originate on the following line. It is recommended
practice to have the remainder of the comment following @ts-ignore explain which error is being
suppressed.
Please note that this comment only suppresses the error reporting, and we recommend you use this com-
ments very sparingly.
The new implementation also brings performance enhancements to watching in tsserver. The watcher logic
has been completely rewritten to respond faster to change events.
Example
Bellow both n and m will be marked as unused, because their values are never read. Previously TypeScript
would only check whether their values were referenced.
class C {
private m: number;
constructor() {
this.m = 0;
}
}
569
typescript
Also functions that are only called within their own bodies are considered unused.
Example
function f() {
f(); // Error: 'f' is declared but its value is never read
}
Go to TOC
570
TypeScript 2.7
Constant-named properties
TypeScript 2.7 adds support for declaring const-named properties on types including ECMAScript symbols.
Example
// Lib
export const SERIALIZE = Symbol("serialize-method-key");
// consumer
Example
let x = {
[Foo]: 100,
[Bar]: "hello"
};
unique symbol
To enable treating symbols as unique literals a new type unique symbol is available. unique symbol is a
subtype of symbol , and are produced only from calling Symbol() or Symbol.for() , or from explicit type
annotations. The new type is only allowed on const declarations and readonly static properties, and in
order to reference a specific unique symbol, you'll have to use the typeof operator. Each reference to a
unique symbol implies a completely unique identity that's tied to a given declaration.
Example
// Works
declare const Foo: unique symbol;
571
typescript
// Also works.
class C {
static readonly StaticSymbol: unique symbol = Symbol();
}
Because each unique symbol has a completely separate identity, no two unique symbol types are as-
signable or comparable to each other.
Example
class C {
foo: number;
bar = "hello";
baz: boolean;
// ~~~
// Error! Property 'baz' has no initializer and is not definitely assigned in
the
// constructor.
constructor() {
this.foo = 42;
}
}
In the above, if we truly meant for baz to potentially be undefined , we should have declared it with the
type boolean | undefined .
There are certain scenarios where properties can be initialized indirectly (perhaps by a helper method or de-
pendency injection library), in which case you can use the new definite assignment assertion modifiers for
your properties (discussed below).
class C {
foo!: number;
// ^
// Notice this '!' modifier.
// This is the "definite assignment assertion"
constructor() {
this.initialize();
}
572
TypeScript 2.7
initialize() {
this.foo = 0;
}
}
Keep in mind that strictPropertyInitialization will be turned on along with other strict mode
flags, which can impact your project. You can set the strictPropertyInitialization setting to false
in your tsconfig.json 's compilerOptions , or --strictPropertyInitialization false on the com-
mand line to turn off this checking.
Example
let x: number;
initialize();
console.log(x + x);
// ~ ~
// Error! Variable 'x' is used before being assigned.
function initialize() {
x = 10;
}
With definite assignment assertions, we can assert that x is really assigned by appending an ! to its
declaration:
// No error!
console.log(x + x);
function initialize() {
x = 10;
}
In a sense, the definite assignment assertion operator is the dual of the non-null assertion operator (in
which expressions are post-fixed with a ! ), which we could also have used in the example.
let x: number;
initialize();
// No error!
console.log(x! + x!);
function initialize() {
x = 10;
573
typescript
In our example, we knew that all uses of x would be initialized so it makes more sense to use definite as-
signment assertions than non-null assertions.
In TypeScript 2.7, tuples of different arities are no longer assignable to each other. Thanks to a pull request
from Kiara Grouwstra, tuple types now encode their arity into the type of their respective length property.
This is accomplished by leveraging numeric literal types, which now allow tuples to be distinct from tuples of
different arities.
Conceptually, you might consider the type [number, string] to be equivalent to the following declaration
of NumStrTuple :
Note that this is a breaking change for some code. If you need to resort to the original behavior in which tu-
ples only enforce a minimum length, you can use a similar declaration that does not explicitly define a
length property, falling back to number .
Note that this does not imply tuples represent immutable arrays, but it is an implied convention.
Consider:
574
TypeScript 2.7
Previously type {} was inferred for obj and the second line subsequently caused an error because obj
would appear to have no properties. That obviously wasn't ideal.
Example
Multiple object literal type inferences for the same type parameter are similarly collapsed into a single nor-
malized union type:
Structurally identical, but distinct, class types are now preserved in union types (instead of eliminating all
but one).
Union type subtype reduction only removes a class type if it is a subclass of and derives from another
class type in the union.
Type checking of the instanceof operator is now based on whether the type of the left operand derives
from the type indicated by the right operand (as opposed to a structural subtype check).
This means that union types and instanceof properly distinguish between structurally identical classes.
Example
class A {}
class B extends A {}
class C extends A {}
class D extends A {
c: string;
}
class E extends D {}
let a1 = [new A(), new B(), new C(), new D(), new E()]; // A[]
let a2 = [new B(), new C(), new D(), new E()]; // (B | C | D)[] (previously B[])
575
typescript
function f1(x: B | C | D) {
if (x instanceof B) {
x; // B (previously B | D)
} else if (x instanceof C) {
x; // C
} else {
x; // D (previously never)
}
}
For a n in x expression, where n is a string literal or string literal type and x is a union type, the "true"
branch narrows to types which have an optional or required property n , and the "false" branch narrows to
types which have an optional or missing property n .
Example
interface A {
a: number;
}
interface B {
b: string;
}
function foo(x: A | B) {
if ("a" in x) {
return x.a;
}
return x.b;
}
Previously CommonJS/AMD/UMD modules were treated in the same way as ES6 modules, resulting in a
couple of problems. Namely:
TypeScript treats a namespace import (i.e. import * as foo from "foo" ) for a CommonJS/AMD/UMD
module as equivalent to const foo = require("foo") .Things are simple here, but they don't work out
if the primary object being imported is a primitive or a class or a function. ECMAScript spec stipulates
that a namespace record is a plain object, and that a namespace import ( foo in the example above) is
not callable, though allowed by TypeScript
576
TypeScript 2.7
Similarly a default import (i.e. import d from "foo" ) for a CommonJS/AMD/UMD module as equiva-
lent to const d = require("foo").default .Most of the CommonJS/AMD/UMD modules available to-
day do not have a default export, making this import pattern practically unusable to import non-ES
modules (i.e. CommonJS/AMD/UMD). For instance import fs from "fs" or import express from
"express" are not allowed.
A namespace import (i.e. import * as foo from "foo" ) is now correctly flagged as uncallable. Calling
it will result in an error.
Default imports to CommonJS/AMD/UMD are now allowed (e.g. import fs from "fs" ), and should
work as expected.
Note: The new behavior is added under a flag to avoid unwarranted breaks to existing code bases.
We highly recommend applying it both to new and existing projects. For existing projects,
namespace imports ( import * as express from "express"; express(); ) will need to be convert-
ed to default imports ( import express from "express"; express(); ).
Example
With esModuleInterop two new helpers are generated __importStar and __importDefault for import
* and import default respectively. For instance input like:
Will generate:
"use strict";
var __importStar =
(this && this.__importStar) ||
function(mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null)
for (var k in mod)
if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k];
result["default"] = mod;
return result;
};
var __importDefault =
(this && this.__importDefault) ||
function(mod) {
return mod && mod.__esModule ? mod : { default: mod };
};
exports.__esModule = true;
var foo = __importStar(require("foo"));
var bar_1 = __importDefault(require("bar"));
577
typescript
Numeric separators
TypeScript 2.7 brings support for ES Numeric Separators. Numeric literals can now be separated into seg-
ments using _ .
Example
Go to TOC
578
TypeScript 2.8
Conditional Types
TypeScript 2.8 introduces conditional types which add the ability to express non-uniform type mappings. A
conditional type selects one of two possible types based on a condition expressed as a type relationship
test:
T extends U ? X : Y
The type above means when T is assignable to U the type is X , otherwise the type is Y .
First, given types T' and U' that are instantiations of T and U where all occurrences of type parame-
ters are replaced with any , if T' is not assignable to U' , the conditional type is resolved to Y .
Intuitively, if the most permissive instantiation of T is not assignable to the most permissive instantia-
tion of U , we know that no instantiation will be and we can just resolve to Y .
Next, for each type variable introduced by an infer (more later) declaration within U collect a set of
candidate types by inferring from T to U (using the same inference algorithm as type inference for
generic functions). For a given infer type variable V , if any candidates were inferred from co-variant
positions, the type inferred for V is a union of those candidates. Otherwise, if any candidates were in-
ferred from contra-variant positions, the type inferred for V is an intersection of those candidates.
Otherwise, the type inferred for V is never .
Then, given a type T'' that is an instantiation of T where all infer type variables are replaced with
the types inferred in the previous step, if T'' is definitely assignable to U , the conditional type is re-
solved to X . The definitely assignable relation is the same as the regular assignable relation, except that
type variable constraints are not considered. Intuitively, when a type is definitely assignable to another
type, we know that it will be assignable for all instantiations of those types.
Otherwise, the condition depends on one or more type variables and the conditional type is deferred.
Example
579
typescript
Example
Example
Notice that T has the additional constraint any[] within the true branch of Boxed<T> and it is therefore
possible to refer to the element type of the array as T[number] . Also, notice how the conditional type is
distributed over the union type in the last example.
The distributive property of conditional types can conveniently be used to filter union types:
type Diff<T, U> = T extends U ? never : T; // Remove types from T that are
assignable to U
type Filter<T, U> = T extends U ? T : never; // Remove types from T that are not
assignable to U
type T30 = Diff<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
type T31 = Filter<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"
type T32 = Diff<string | number | (() => void), Function>; // string | number
type T33 = Filter<string | number | (() => void), Function>; // () => void
type NonNullable<T> = Diff<T, null | undefined>; // Remove null and undefined from
T
580
TypeScript 2.8
Conditional types are particularly useful when combined with mapped types:
type FunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? K : never;
}[keyof T];
type FunctionProperties<T> = Pick<T, FunctionPropertyNames<T>>;
type NonFunctionPropertyNames<T> = {
[K in keyof T]: T[K] extends Function ? never : K;
}[keyof T];
type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>;
interface Part {
id: number;
name: string;
subparts: Part[];
updatePart(newName: string): void;
}
Similar to union and intersection types, conditional types are not permitted to reference themselves recur-
sively. For example the following is an error.
Example
For example, the following extracts the return type of a function type:
Conditional types can be nested to form a sequence of pattern matches that are evaluated in order:
581
typescript
? U
: T;
The following example demonstrates how multiple candidates for the same type variable in co-variant posi-
tions causes a union type to be inferred:
Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection
type to be inferred:
type Bar<T> = T extends { a: (x: infer U) => void; b: (x: infer U) => void }
? U
: never;
type T20 = Bar<{ a: (x: string) => void; b: (x: string) => void }>; // string
type T21 = Bar<{ a: (x: string) => void; b: (x: number) => void }>; // string &
number
When inferring from a type with multiple call signatures (such as the type of an overloaded function), infer-
ences are made from the last signature (which, presumably, is the most permissive catch-all case). It is not
possible to perform overload resolution based on a list of argument types.
It is not possible to use infer declarations in constraint clauses for regular type parameters:
type ReturnType<T extends (...args: any[]) => infer R> = R; // Error, not
supported
However, much the same effect can be obtained by erasing the type variables in the constraint and instead
specifying a conditional type:
582
TypeScript 2.8
Example
type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"
type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"
type T02 = Exclude<string | number | (() => void), Function>; // string | number
type T03 = Extract<string | number | (() => void), Function>; // () => void
class C {
x = 0;
y = 0;
}
Note: The Exclude type is a proper implementation of the Diff type suggested here. We've used
the name Exclude to avoid breaking existing code that defines a Diff , plus we feel that name bet-
ter conveys the semantics of the type. We did not include the Omit<T, K> type because it is trivially
written as Pick<T, Exclude<keyof T, K>> .
583
typescript
TypeScript 2.8 adds the ability for a mapped type to either add or remove a particular modifier. Specifically,
a readonly or ? property modifier in a mapped type can now be prefixed with either + or - to indicate
that the modifier should be added or removed.
Example
A modifier with no + or - prefix is the same as a modifier with a + prefix. So, the ReadonlyPartial<T>
type above corresponds to
Using this ability, lib.d.ts now has a new Required<T> type. This type strips ? modifiers from all prop-
erties of T , thus making all properties required.
Example
Note that in strictNullChecks mode, when a homomorphic mapped type removes a ? modifier from a
property in the underlying type it also removes undefined from the type of that property:
Example
Example
type A = { a: string };
type B = { b: string };
584
TypeScript 2.8
Assignments at the top-level should behave the same way; in other words, a var or const declaration is
not required.
var C = (function() {
function C(n) {
this.p = n;
}
return C;
})();
C.staticProperty = 1;
Defaulted declarations
"Defaulted declarations" allow initializers that reference the declared name in the left side of a logical or:
my = window.my || {};
my.app = my.app || {};
Prototype assignment
You can assign an object literal directly to the prototype property. Individual prototype assignments still
work too:
var C = function(p) {
this.p = p;
};
C.prototype = {
m() {
console.log(this.p);
}
};
C.prototype.q = function(r) {
return this.p === r;
};
585
typescript
Example
Generates:
New --emitDeclarationOnly
emitDeclarationOnly allows for only generating declaration files; .js / .jsx output generation will be
skipped with this flag. The flag is useful when the .js output generation is handled by a different transpiler
like Babel.
Go to TOC
586
TypeScript 2.9
Changes include:
An index type keyof T for some type T is a subtype of string | number | symbol .
A mapped type { [P in K]: XXX } permits any K assignable to string | number | symbol .
In a for...in statement for an object of a generic type T , the inferred type of the iteration variable
was previously keyof T but is now Extract<keyof T, string> . (In other words, the subset of keyof
T that includes only string-like values.)
If X contains a string index signature, keyof X is a union of string , number , and the literal types
representing symbol-like properties, otherwise
If X contains a numeric index signature, keyof X is a union of number and the literal types represent-
ing string-like and symbol-like properties, otherwise
keyof X is a union of the literal types representing string-like, number-like, and symbol-like properties.
Where:
String-like properties of an object type are those declared using an identifier, a string literal, or a com-
puted property name of a string literal type.
Number-like properties of an object type are those declared using a numeric literal or computed property
name of a numeric literal type.
Symbol-like properties of an object type are those declared using a computed property name of a unique
symbol type.
In a mapped type { [P in K]: XXX } , each string literal type in K introduces a property with a string
name, each numeric literal type in K introduces a property with a numeric name, and each unique symbol
type in K introduces a property with a unique symbol name. Furthermore, if K includes type string , a
string index signature is introduced, and if K includes type number , a numeric index signature is
introduced.
Example
const c = "c";
const d = 10;
const e = Symbol();
const enum E1 {
A,
B,
C,
}
const enum E2 {
587
typescript
A = "A",
B = "B",
C = "C",
}
type Foo = {
a: string; // String-like name
5: string; // Number-like name
[c]: string; // String-like name
[d]: string; // Number-like name
[e]: string; // Symbol-like name
[E1.A]: string; // Number-like name
[E2.A]: string; // String-like name
};
Since keyof now reflects the presence of a numeric index signature by including type number in the key
type, mapped types such as Partial<T> and Readonly<T> work correctly when applied to object types
with numeric index signatures:
type Arrayish<T> = {
length: number;
[x: number]: T;
};
Furthermore, with the keyof operator's support for number and symbol named keys, it is now possible to
abstract over access to properties of objects that are indexed by numeric literals (such as numeric enum
types) and unique symbols.
const enumToStringMap = {
[Enum.A]: "Name A",
[Enum.B]: "Name B",
[Enum.C]: "Name C",
};
const symbolToNumberMap = {
[sym1]: 1,
[sym2]: 2,
[sym3]: 3,
588
TypeScript 2.9
};
This is a breaking change; previously, the keyof operator and mapped types only supported string
named properties. Code that assumed values typed with keyof T were always string s, will now be
flagged as error.
Example
Recommendations
If your functions are only able to handle string named property keys, use Extract<keyof T, string>
in the declaration:
If your functions are open to handling all property keys, then the changes should be done down-stream:
Example
589
typescript
TypeScript 2.9 allows passing generic type arguments to tagged template strings.
Example
interface MyProps {
name: string;
age: number;
}
styledComponent<MyProps>`
font-size: 1.5em;
text-align: center;
color: palevioletred;
`;
// inference fails because 'number' and 'string' are both candidates that conflict
let a = tag<string | number>`${100} ${"hello"}`;
import types
Modules can import types declared in other modules. But non-module global scripts cannot access types de-
clared in modules. Enter import types.
Using import("mod") in a type annotation allows for reaching in a module and accessing its exported dec-
laration without importing it.
Example
// module.d.ts
// global-script.ts
590
TypeScript 2.9
This also works in JSDoc comments to refer to types from other modules in .js :
// a.js
/**
* @param p { import("./module").Pet }
*/
function walk(p) {
console.log(`Walking ${p.name}...`);
}
For instance:
With TypeScript 2.9, no errors are reported, and now the generated file looks like:
The type of import.meta is the global ImportMeta type which is defined in lib.es5.d.ts . This interface
is extremely limited. Adding well-known properties for Node or browsers requires interface merging and
possibly a global augmentation depending on the context.
Example
Assuming that __dirname is always available on import.meta , the declaration would be done through re-
opening ImportMeta interface:
// node.d.ts
interface ImportMeta {
__dirname: string;
}
591
typescript
import.meta is only allowed when targeting ESNext modules and ECMAScript targets.
New --resolveJsonModule
Often in Node.js applications a .json is needed. With TypeScript 2.9, resolveJsonModule allows for im-
porting, extracting types from and generating .json files.
Example
// settings.json
{
"repo": "TypeScript",
"dry": false,
"debug": false
}
// a.ts
// tsconfig.json
{
"compilerOptions": {
"module": "commonjs",
"resolveJsonModule": true,
"esModuleInterop": true
}
}
Use --pretty false on the command line or set "pretty": false in your tsconfig.json to disable
pretty output.
New --declarationMap
Enabling declarationMap alongside declaration causes the compiler to emit .d.ts.map files alongside
the output .d.ts files. Language Services can also now understand these map files, and uses them to map
declaration-file based definition locations to their original source, when available.
In other words, hitting go-to-definition on a declaration from a .d.ts file generated with declarationMap
will take you to the source file ( .ts ) location where that declaration was defined, and not to the .d.ts .
Go to TOC
592
TypeScript 3.0
Project References
TypeScript 3.0 introduces a new concept of project references. Project references allow TypeScript projects
to depend on other TypeScript projects - specifically, allowing tsconfig.json files to reference other
tsconfig.json files. Specifying these dependencies makes it easier to split your code into smaller
projects, since it gives TypeScript (and tools around it) a way to understand build ordering and output
structure.
TypeScript 3.0 also introduces a new mode for tsc, the --build flag, that works hand-in-hand with project
references to enable faster TypeScript builds.
With these features it becomes possible to strongly type a number of higher-order functions that transform
functions and their parameter lists.
593
typescript
Example
In the declaration of f2 above, type inference infers types number , [string, boolean] and void for
T , U and V respectively.
Note that when a tuple type is inferred from a sequence of parameters and later expanded into a parameter
list, as is the case for U , the original parameter names are used in the expansion (however, the names
have no semantic meaning and are not otherwise observable).
Example
In strictNullChecks mode, a ? modifier automatically includes undefined in the element type, similar
to optional parameters.
A tuple type permits an element to be omitted if it has a postfix ? modifier on its type and all elements to
the right of it also have ? modifiers.
When tuple types are inferred for rest parameters, optional parameters in the source become optional tuple
elements in the inferred type.
594
TypeScript 3.0
The length property of a tuple type with optional elements is a union of numeric literal types representing
the possible lengths. For example, the type of the length property in the tuple type [number, string?,
boolean?] is 1 | 2 | 3 .
Example
The type of the length property of a tuple type with a rest element is number .
Example
595
typescript
// keyof unknown
596
TypeScript 3.0
let x: unknown;
x = 123;
x = "hello";
x = [1, 2, 3];
x = new Error();
x = x;
x = pAny;
x = pNever;
x = pT;
}
function f25() {
let x: unknown;
let y = x;
}
597
typescript
class C1 {
a: string; // Error
b: unknown;
c: any;
}
TypeScript 3.0 adds support for a new type alias in the JSX namespace called
LibraryManagedAttributes . This helper type defines a transformation on the component's Props type,
before using to check a JSX expression targeting it; thus allowing customization like: how conflicts between
provided props and inferred props are handled, how inferences are mapped, how optionality is handled, and
how inferences from differing places should be combined.
In short using this general type, we can model React's specific behavior for things like defaultProps and,
to some extent, propTypes .
Caveats
Explicit types on defaultProps
The default-ed properties are inferred from the defaultProps property type. If an explicit type annotation
is added, e.g. static defaultProps: Partial<Props>; the compiler will not be able to identify which
properties have defaults (since the type of defaultProps include all properties of Props ).
Use static defaultProps: Pick<Props, "name">; as an explicit type annotation instead, or do not add
a type annotation as done in the example above.
For function components (formerly known as SFCs) use ES2015 default initializers:
598
TypeScript 3.0
Changes to @types/React
Built-in lib files are referenced in the same fashion as the lib compiler option in tsconfig.json (e.g. use
lib="es2015" and not lib="lib.es2015.d.ts" , etc.).
For declaration file authors who relay on built-in types, e.g. DOM APIs or built-in JS run-time constructors
like Symbol or Iterable , triple-slash-reference lib directives are the recommended. Previously these .d.ts
files had to add forward/duplicate declarations of such types.
Example
Using /// <reference lib="es2017.string" /> to one of the files in a compilation is equivalent to com-
piling with --lib es2017.string .
"foo".padStart(4);
Go to TOC
599
typescript
MapToPromise takes a type T , and when that type is a tuple like Coordinate , only the numeric proper-
ties are converted. In [number, number] , there are two numerically named properties: 0 and 1 . When
given a tuple like that, MapToPromise will create a new tuple where the 0 and 1 properties are
Promise s of the original type. So the resulting type PromiseCoordinate ends up with the type
[Promise<number>, Promise<number>] .
function readImage(path: string, callback: (err: any, image: Image) => void) {
// ...
}
Here, we have a function readImage which reads an image in a non-blocking asynchronous way. In addi-
tion to readImage , we've provided a convenience function on readImage itself called readImage.sync .
While ECMAScript exports are often a better way of providing this functionality, this new support allows code
written in this style to "just work" in TypeScript. Additionally, this approach for property declarations allows
us to express common patterns like defaultProps and propTypes on React function components (for-
merly known as SFCs).
FooComponent.defaultProps = {
name: "(anonymous)",
};
[1]
More specifically, homomorphic mapped types like in the above form.
600
TypeScript 3.1
You can read about it in the Publishing section of the declaration files section
Go to TOC
601
typescript
strictBindCallApply
TypeScript 3.2 introduces a new strictBindCallApply compiler option (in the strict family of options)
with which the bind , call , and apply methods on function objects are strongly typed and strictly
checked.
This is achieved by introducing two new types, CallableFunction and NewableFunction , in lib.d.ts .
These types contain specialized generic method declarations for bind , call , and apply for regular func-
tions and constructor functions, respectively. The declarations use generic rest parameters (see #24897) to
capture and reflect parameter lists in a strongly typed manner. In strictBindCallApply mode these dec-
larations are used in place of the (very permissive) declarations provided by type Function .
Caveats
Since the stricter checks may uncover previously unreported errors, this is a breaking change in strict
mode.
Additionally, another caveat of this new functionality is that due to certain limitations, bind , call , and
apply can't yet fully model generic functions or functions that have overloads. When using these methods
on a generic function, type parameters will be substituted with the empty object type ( {} ), and when used
on a function with overloads, only the last overload will ever be modeled.
Property assignments and non-generic spread expressions are merged to the greatest extent possible on ei-
ther side of a generic spread expression. For example:
602
TypeScript 3.2
Non-generic spread expressions continue to be processed as before: Call and construct signatures are
stripped, only non-method properties are preserved, and for properties with the same name, the type of the
rightmost property is used. This contrasts with intersection types which concatenate call and construct sig-
natures, preserve all properties, and intersect the types of properties with the same name. Thus, spreads of
the same types may produce different results when they are created through instantiation of generic types:
BigInt
BigInts are part of an upcoming proposal in ECMAScript that allow us to model theoretically arbitrarily large
integers. TypeScript 3.2 brings type-checking for BigInts, as well as support for emitting BigInt literals when
targeting esnext .
BigInt support in TypeScript introduces a new primitive type called the bigint (all lowercase). You can get
a bigint by calling the BigInt() function or by writing out a BigInt literal by adding an n to the end of
any integer numeric literal:
603
typescript
fibonacci(10000n);
While you might imagine close interaction between number and bigint , the two are separate domains.
As specified in ECMAScript, mixing number s and bigint s in arithmetic operations is an error. You'll have
to explicitly convert values to BigInt s.
Also important to note is that bigint s produce a new string when using the typeof operator: the string
"bigint" . Thus, TypeScript correctly narrows using typeof as you'd expect.
We'd like to extend a huge thanks to Caleb Sander for all the work on this feature. We're grateful for the
contribution, and we're sure our users are too!
Caveats
As we mentioned, BigInt support is only available for the esnext target. It may not be obvious, but be-
cause BigInts have different behavior for mathematical operators like + , - , * , etc., providing functionality
for older targets where the feature doesn't exist (like es2017 and below) would involve rewriting each of
these operations. TypeScript would need to dispatch to the correct behavior depending on the type, and so
every addition, string concatenation, multiplication, etc. would involve a function call.
For that reason, we have no immediate plans to provide downleveling support. On the bright side, Node 11
and newer versions of Chrome already support this feature, so you'll be able to use BigInts there when tar-
geting esnext .
604
TypeScript 3.2
Certain targets may include a polyfill or BigInt-like runtime object. For those purposes you may want to add
esnext.bigint to the lib setting in your compiler options.
As a result, TypeScript 3.2 considers the error property in the following example to be a discriminant,
whereas before it wouldn't since Error isn't a singleton type. Thanks to this, narrowing works correctly in
the body of the unwrap function.
{
"extends": "@my-team/tsconfig-base",
"include": ["./**/*"],
"compilerOptions": {
// Override certain options on a project-by-project basis.
"strictBindCallApply": false
}
}
Here, TypeScript will climb up node_modules folders looking for a @my-team/tsconfig-base package. For
each of those packages, TypeScript will first check whether package.json contains a "tsconfig" field,
and if it does, TypeScript will try to load a configuration file from that field. If neither exists, TypeScript will
try to read from a tsconfig.json at the root. This is similar to the lookup process for .js files in pack-
ages that Node uses, and the .d.ts lookup process that TypeScript already uses.
This feature can be extremely useful for bigger organizations, or projects with lots of distributed
dependencies.
605
typescript
// @ts-check
obj.x.toLowercase();
// ~~~~~~~~~~~
// error:
// Property 'toLowercase' does not exist on type 'string'.
// Did you mean 'toLowerCase'?
obj.x = "world";
// ~
// error:
// Cannot assign to 'x' because it is a read-only property.
Go to TOC
606
TypeScript 3.3
type FruitEater = (fruit: Fruit) => number; // eats and ranks the fruit
type ColorConsumer = (color: Color) => string; // consumes and describes the
colors
However, in the above example, both FruitEater s and ColorConsumer s should be able to take the string
"orange" , and return either a number or a string .
type FruitEater = (fruit: Fruit) => number; // eats and ranks the fruit
type ColorConsumer = (color: Color) => string; // consumes and describes the
colors
In TypeScript 3.3, the parameters of these signatures are intersected together to create a new signature.
In the example above, the parameters fruit and color are intersected together to a new parameter of
type Fruit & Color . Fruit & Color is really the same as ("apple" | "orange") & ("red" | "or‐
ange") which is equivalent to ("apple" & "red") | ("apple" & "orange") | ("orange" & "red") |
("orange" & "orange") . Each of those impossible intersections reduces to never , and we're left with
"orange" & "orange" which is just "orange" .
607
typescript
Caveats
This new behavior only kicks in when at most one type in the union has multiple overloads, and at most one
type in the union has a generic signature. That means methods on number[] | string[] like map (which
is generic) still won't be callable.
On the other hand, methods like forEach will now be callable, but under noImplicitAny there may be
some issues.
interface Dog {
kind: "dog";
dogProp: any;
}
interface Cat {
kind: "cat";
catProp: any;
}
catOrDogArray.forEach(animal => {
// ~~~~~~ error!
// Parameter 'animal' implicitly has an 'any' type.
});
This is still strictly more capable in TypeScript 3.3, and adding an explicit type annotation will work.
interface Dog {
kind: "dog";
dogProp: any;
}
interface Cat {
kind: "cat";
catProp: any;
}
608
TypeScript 3.3
TypeScript 2.7 also introduced --watch mode builds via a new incremental "builder" API. In a similar vein,
the entire idea is that this mode only re-checks and re-emits changed files or files whose dependencies
might impact type-checking. You can think of this as optimizing intra-project builds.
Prior to 3.3, building composite projects using --build --watch actually didn't use this incremental file
watching infrastructure. An update in one project under --build --watch mode would force a full build of
that project, rather than determining which files within that project were affected.
In TypeScript 3.3, --build mode's --watch flag does leverage incremental file watching as well. That can
mean signficantly faster builds under --build --watch . In our testing, this functionality has resulted in a
reduction of 50% to 75% in build times of the original --build --watch times. You can read more on
the original pull request for the change to see specific numbers, but we believe most composite project
users will see significant wins here.
Go to TOC
609
typescript
// tsconfig.json
{
"compilerOptions": {
"incremental": true,
"outDir": "./lib"
},
"include": ["./src"]
}
By default with these settings, when we run tsc , TypeScript will look for a file called .tsbuildinfo in the
output directory ( ./lib ). If ./lib/.tsbuildinfo doesn't exist, it'll be generated. But if it does, tsc will
try to use that file to incrementally type-check and update our output files.
These .tsbuildinfo files can be safely deleted and don't have any impact on our code at runtime -
they're purely used to make compilations faster. We can also name them anything that we want, and place
them anywhere we want using the tsBuildInfoFile option.
// front-end.tsconfig.json
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./buildcache/front-end",
"outDir": "./lib"
},
"include": ["./src"]
}
Composite projects
Part of the intent with composite projects ( tsconfig.json s with composite set to true ) is that refer-
ences between different projects can be built incrementally. As such, composite projects will always pro-
duce .tsbuildinfo files.
outFile
When outFile is used, the build information file's name will be based on the output file's name. As an ex-
ample, if our output JavaScript file is ./output/foo.js , then under the incremental flag, TypeScript will
generate the file ./output/foo.tsbuildinfo . As above, this can be controlled with the tsBuildInfo‐
File option.
610
TypeScript 3.4
To get more specific, let's build up some motivation and consider the following compose function:
function compose<A, B, C>(f: (arg: A) => B, g: (arg: B) => C): (arg: A) => C {
return (x) => g(f(x));
}
f which takes some argument (of type A ) and returns a value of type B
g which takes an argument of type B (the type f returned), and returns a value of type C
compose then returns a function which feeds its argument through f and then g .
When calling this function, TypeScript will try to figure out the types of A , B , and C through a process
called type argument inference. This inference process usually works pretty well:
interface Person {
name: string;
age: number;
}
The inference process is fairly straightforward here because getDisplayName and getLength use types
that can easily be referenced. However, in TypeScript 3.3 and earlier, generic functions like compose didn't
work so well when passed other generic functions.
interface Box<T> {
value: T;
}
611
typescript
makeBoxedArray("hello!").value[0].toUpperCase();
// ~~~~~~~~~~~
// error: Property 'toUpperCase' does not exist on type '{}'.
In older versions, TypeScript would infer the empty object type ( {} ) when inferring from other type vari-
ables like T and U .
During type argument inference in TypeScript 3.4, for a call to a generic function that returns a function
type, TypeScript will, as appropriate, propagate type parameters from generic function arguments onto the
resulting function type.
Notice that T has been propagated from makeArray into the resulting type's type parameter list. This
means that genericity from compose 's arguments has been preserved and our makeBoxedArray sample
will just work!
interface Box<T> {
value: T;
}
For more details, you can read more at the original change.
612
TypeScript 3.4
While it's good practice to use ReadonlyArray over Array when no mutation is intended, it's often been a
pain given that arrays have a nicer syntax. Specifically, number[] is a shorthand version of
Array<number> , just as Date[] is a shorthand for Array<Date> .
TypeScript 3.4 introduces a new syntax for ReadonlyArray using a new readonly modifier for array
types.
readonly tuples
TypeScript 3.4 also introduces new support for readonly tuples. We can prefix any tuple type with the
readonly keyword to make it a readonly tuple, much like we now can with array shorthand syntax. As
you might expect, unlike ordinary tuples whose slots could be written to, readonly tuples only permit
reading from those positions.
The same way that ordinary tuples are types that extend from Array - a tuple with elements of type T1 ,
T2 , ... Tn extends from Array< T1 | T2 | ... Tn > - readonly tuples are types that extend from
ReadonlyArray . So a readonly tuple with elements T1 , T2 , ... Tn extends from ReadonlyArray< T1 |
T2 | ... Tn .
interface Box<T> {
value: T;
}
type Boxify<T> = {
[K in keyof T]: Box<T[K]>;
};
613
typescript
// { a: Box<string>, b: Box<number> }
type A = Boxify<{ a: string; b: number }>;
// Array<Box<number>>
type B = Boxify<number[]>;
// [Box<string>, Box<number>]
type C = Boxify<[string, boolean]>;
Unfortunately, mapped types like the Readonly utility type were effectively no-ops on array and tuple
types.
// lib.d.ts
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
// number[]
type B = Readonly<number[]>;
// [string, boolean]
type C = Readonly<[string, boolean]>;
In TypeScript 3.4, the readonly modifier in a mapped type will automatically convert array-like types to
their corresponding readonly counterparts.
// readonly number[]
type B = Readonly<number[]>;
Similarly, you could write a utility type like Writable mapped type that strips away readonly -ness, and
that would convert readonly array containers back to their mutable equivalents.
type Writable<T> = {
-readonly [K in keyof T]: T[K];
};
// { a: string, b: number }
type A = Writable<{
readonly a: string;
readonly b: number;
}>;
// number[]
type B = Writable<readonly number[]>;
614
TypeScript 3.4
// [string, boolean]
type C = Writable<readonly [string, boolean]>;
Caveats
Despite its appearance, the readonly type modifier can only be used for syntax on array types and tuple
types. It is not a general-purpose type operator.
const assertions
TypeScript 3.4 introduces a new construct for literal values called const assertions. Its syntax is a type as-
sertion with const in place of the type name (e.g. 123 as const ). When we construct new literal expres-
sions with const assertions, we can signal to the language that
no literal types in that expression should be widened (e.g. no going from "hello" to string )
object literals get readonly properties
array literals become readonly tuples
// Type '"hello"'
let x = "hello" as const;
Outside of .tsx files, the angle bracket assertion syntax can also be used.
// Type '"hello"'
let x = <const>"hello";
This feature means that types that would otherwise be used just to hint immutability to the compiler can of-
ten be omitted.
615
typescript
] as const;
return result;
}
Notice the above needed no type annotations. The const assertion allowed TypeScript to take the most
specific type of the expression.
This can even be used to enable enum -like patterns in plain JavaScript code if you choose not to use
TypeScript's enum construct.
export default {
red: "RED",
blue: "BLUE",
green: "GREEN",
} as const;
Caveats
One thing to note is that const assertions can only be applied immediately on simple literal expressions.
// Works!
let c = Math.random() < 0.5 ? (0 as const) : (1 as const);
let d = 3_600_000 as const;
Another thing to keep in mind is that const contexts don't immediately convert an expression to be fully
immutable.
let foo = {
name: "foo",
contents: arr,
} as const;
616
TypeScript 3.4
foo.contents.push(5); // ...works!
For more details, you can check out the respective pull request.
// in a global file:
Note that global variables declared with let and const don't show up on globalThis .
It's also important to note that TypeScript doesn't transform references to globalThis when compiling to
older versions of ECMAScript. As such, unless you're targeting evergreen browsers (which already support
globalThis ), you may want to use an appropriate polyfill instead.
For more details on the implementation, see the feature's pull request.
Go to TOC
617
typescript
Speed improvements
TypeScript 3.5 introduces several optimizations around type-checking and incremental builds.
Type-checking speed-ups
TypeScript 3.5 contains certain optimizations over TypeScript 3.4 for type-checking more efficiently. These
improvements are significantly more pronounced in editor scenarios where type-checking drives operations
like code completion lists.
--incremental improvements
TypeScript 3.5 improves on 3.4's incremental build mode, by saving information about how the state of
the world was calculated - compiler settings, why files were looked up, where files were found, etc. In sce-
narios involving hundreds of projects using TypeScript's project references in --build mode, we've found
that the amount of time rebuilding can be reduced by as much as 68% compared to TypeScript 3.4!
type Person = {
name: string;
age: number;
location: string;
};
// equivalent to
type QuantumPerson = {
name: string;
age: number;
};
Here we were able to copy over all the properties of Person except for location using the Omit helper.
For more details, see the pull request on GitHub to add Omit , as well as the change to use Omit for object
rest.
618
TypeScript 3.5
type Point = {
x: number;
y: number;
};
type Label = {
name: string;
};
Previously, a non-disciminated union wouldn't have any excess property checking done on its members, and
as a result, the incorrectly typed name property slipped by.
In TypeScript 3.5, the type-checker at least verifies that all the provided properties belong to some union
member and have the appropriate type, meaning that the sample above correctly issues an error.
Note that partial overlap is still permitted as long as the property types are valid.
This mode adds flexibility for mixing and matching the way 3rd party libraries, where globals that libraries
declare can always be consumed, even from within modules.
619
typescript
target = source;
That's because S isn't assignable to { done: false, value: number } nor { done: true, value:
number } . Why? Because the done property in S isn't specific enough - it's boolean whereas each con-
stituent of T has a done property that's specifically true or false . That's what we meant by each con-
stituent type being checked in isolation: TypeScript doesn't just union each property together and see if S
is assignable to that. If it did, some bad code could get through like the following:
interface Foo {
kind: "foo";
value: string;
}
interface Bar {
kind: "bar";
value: number;
}
However, this was a bit overly strict for the original example. If you figure out the precise type of any possi-
ble value of S , you can actually see that it matches the types in T exactly.
In TypeScript 3.5, when assigning to types with discriminant properties like in T , the language actually will
go further and decompose types like S into a union of every possible inhabitant type. In this case, since
boolean is a union of true and false , S will be viewed as a union of { done: false, value: number
} and { done: true, value: number } .
For more details, you can see the original pull request on GitHub.
function compose<T, U, V>(f: (x: T) => U, g: (y: U) => V): (x: T) => V {
return x => g(f(x));
}
620
TypeScript 3.5
Instead of a relatively useless type like (x: {}) => Box<{}[]> , which older versions of the language
would infer, TypeScript 3.4's inference allows newFn to be generic. Its new type is <T>(x: T) =>
Box<T[]> .
class Box<T> {
kind: "box";
value: T;
constructor(value: T) {
this.value = value;
}
}
class Bag<U> {
kind: "bag";
value: U;
constructor(value: U) {
this.value = value;
}
}
In addition to compositional patterns like the above, this new inference on generic constructors means that
functions that operate on class components in certain UI libraries like React can more correctly operate on
generic class components.
621
typescript
Go to TOC
622
TypeScript 3.6
Stricter Generators
TypeScript 3.6 introduces stricter checking for iterators and generator functions. In earlier versions, users of
generators had no way to differentiate whether a value was yielded or returned from a generator.
function* foo() {
if (Math.random() < 0.5) yield 100;
return "Finished!";
}
Additionally, generators just assumed the type of yield was always any .
function* bar() {
let x: { hello(): void } = yield;
x.hello();
}
In TypeScript 3.6, the checker now knows that the correct type for curr.value should be string in our
first example, and will correctly error on our call to next() in our last example. This is thanks to some
changes in the Iterator and IteratorResult type declarations to include a few new type parameters,
and to a new type that TypeScript uses to represent generators called the Generator type.
The Iterator type now allows users to specify the yielded type, the returned type, and the type that
next can accept.
Building on that work, the new Generator type is an Iterator that always has both the return and
throw methods present, and is also iterable.
623
typescript
To allow differentiation between returned values and yielded values, TypeScript 3.6 converts the
IteratorResult type to a discriminated union type:
interface IteratorYieldResult<TYield> {
done?: false;
value: TYield;
}
interface IteratorReturnResult<TReturn> {
done: true;
value: TReturn;
}
In short, what this means is that you'll be able to appropriately narrow down values from iterators when
dealing with them directly.
To correctly represent the types that can be passed in to a generator from calls to next() , TypeScript 3.6
also infers certain uses of yield within the body of a generator function.
function* foo() {
let x: string = yield;
console.log(x.toUpperCase());
}
let x = foo();
x.next(); // first call to 'next' is always ignored
x.next(42); // error! 'number' is not assignable to 'string'
If you'd prefer to be explicit, you can also enforce the type of values that can be returned, yielded, and
evaluated from yield expressions using an explicit return type. Below, next() can only be called with
boolean s, and depending on the value of done , value is either a string or a number .
/**
* - yields numbers
* - returns strings
* - can be passed in booleans
*/
function* counter(): Generator<number, string, boolean> {
let i = 0;
while (true) {
if (yield i++) {
break;
}
}
return "done!";
}
624
TypeScript 3.6
console.log(curr.value.toUpperCase());
// prints:
//
// 0
// 1
// 2
// 3
// 4
// 5
// DONE!
For more details on the change, see the pull request here.
[...Array(5)];
However, TypeScript would instead transform the original code into this code:
Array(5).slice();
which is slightly different. Array(5) produces an array with a length of 5, but with no defined property
slots.
TypeScript 3.6 introduces a new __spreadArrays helper to accurately model what happens in ECMAScript
2015 in older targets outside of downlevelIteration . __spreadArrays is also available in tslib.
For example, it's often very common to forget to .then() or await the contents of a Promise before
passing it to another function. TypeScript's error messages are now specialized, and inform the user that
perhaps they should consider using the await keyword.
interface User {
name: string;
age: number;
625
typescript
location: string;
}
It's also common to try to access a method before await -ing or .then() -ing a Promise . This is another
example, among many others, where we're able to do better.
For more details, see the originating issue, as well as the pull requests that link back to it.
// This module:
console.log(import.meta.url);
626
TypeScript 3.6
As a result, users can write getters and setters in ambient contexts in TypeScript 3.6.
In TypeScript 3.7, the compiler itself will take advantage of this feature so that generated .d.ts files will
also emit get / set accessors.
One advantage of this is that the callable constructor pattern can be easily expressed while also allowing
namespaces to merge with these declarations (since var declarations can't merge with namespace s).
In TypeScript 3.7, the compiler will take advantage of this feature so that .d.ts files generated from .js
files can appropriately capture both the callability and constructability of a class-like function.
627
typescript
For creating incremental builds, users can leverage the createIncrementalProgram and createIncre‐
mentalCompilerHost APIs. Users can also re-hydrate old program instances from .tsbuildinfo files gen-
erated by this API using the newly exposed readBuilderProgram function, which is only meant to be used
as for creating new programs (i.e. you can't modify the returned instance - it's only meant to be used for
the oldProgram parameter in other create*Program functions).
For leveraging project references, a new createSolutionBuilder function has been exposed, which re-
turns an instance of the new type SolutionBuilder .
For more details on these APIs, you can see the original pull request.
TypeScript is now smart enough to detect whether your file uses semicolons when applying these sorts of
edits. If your file generally lacks semicolons, TypeScript won't add one.
TypeScript 3.6 is now a bit smarter about looking at your existing imports before deciding on how to auto-
import other modules. You can see more details in the original pull request here.
628
TypeScript 3.6
The target option (allowing users to switch out of es5 to es3 , es2015 , esnext , etc.)
All the strictness flags (including just strict )
Support for plain JavaScript files (using allowJS and optionally checkJs )
These options also persist when sharing links to playground samples, allowing users to more reliably share
examples without having to tell the recipient "oh, don't forget to turn on the noImplicitAny option!".
In the near future, we're going to be refreshing the playground samples, adding JSX support, and polishing
automatic type acquisition, meaning that you'll be able to see the same experience on the playground as
you'd get in your personal editor.
As we improve the playground and the website, we welcome feedback and pull requests on GitHub!
Go to TOC
629
typescript
Optional Chaining
Playground
Optional chaining is issue #16 on our issue tracker. For context, there have been over 23,000 issues on the
TypeScript issue tracker since then.
At its core, optional chaining lets us write code where TypeScript can immediately stop running some ex-
pressions if we run into a null or undefined . The star of the show in optional chaining is the new ?. op-
erator for optional property accesses. When we write code like
let x = foo?.bar.baz();
this is a way of saying that when foo is defined, foo.bar.baz() will be computed; but when foo is
null or undefined , stop what we're doing and just return undefined ."
More plainly, that code snippet is the same as writing the following.
Note that if bar is null or undefined , our code will still hit an error accessing baz . Likewise, if baz is
null or undefined , we'll hit an error at the call site. ?. only checks for whether the value on the left of
it is null or undefined - not any of the subsequent properties.
You might find yourself using ?. to replace a lot of code that performs repetitive nullish checks using the
&& operator.
// Before
if (foo && foo.bar && foo.bar.baz) {
// ...
}
// After-ish
if (foo?.bar?.baz) {
// ...
}
Keep in mind that ?. acts differently than those && operations since && will act specially on "falsy" values
(e.g. the empty string, 0 , NaN , and, well, false ), but this is an intentional feature of the construct. It
doesn't short-circuit on valid data like 0 or empty strings.
Optional chaining also includes two other operations. First there's the optional element access which acts
similarly to optional property accesses, but allows us to access non-identifier properties (e.g. arbitrary
strings, numbers, and symbols):
/**
* Get the first element of the array if we have an array.
* Otherwise return undefined.
*/
function tryGetFirstElement<T>(arr?: T[]) {
return arr?.[0];
630
TypeScript 3.7
// equivalent to
// return (arr === null || arr === undefined) ?
// undefined :
// arr[0];
}
There's also optional call, which allows us to conditionally call expressions if they're not null or unde‐
fined .
return result;
}
The "short-circuiting" behavior that optional chains have is limited property accesses, calls, element access-
es - it doesn't expand any further out from these expressions. In other words,
doesn't stop the division or someComputation() call from occurring. It's equivalent to
let temp = foo === null || foo === undefined ? undefined : foo.bar;
That might result in dividing undefined , which is why in strictNullChecks , the following is an error.
More more details, you can read up on the proposal and view the original pull request.
Nullish Coalescing
Playground
The nullish coalescing operator is another upcoming ECMAScript feature that goes hand-in-hand with op-
tional chaining, and which our team has been involved with championing in TC39.
You can think of this feature - the ?? operator - as a way to "fall back" to a default value when dealing with
null or undefined . When we write code like
631
typescript
this is a new way to say that the value foo will be used when it's "present"; but when it's null or unde‐
fined , calculate bar() in its place.
let x = foo !== null && foo !== undefined ? foo : bar();
The ?? operator can replace uses of || when trying to use a default value. For example, the following
code snippet tries to fetch the volume that was last saved in localStorage (if it ever was); however, it has
a bug because it uses || .
function initializeAudio() {
let volume = localStorage.volume || 0.5;
// ...
}
When localStorage.volume is set to 0 , the page will set the volume to 0.5 which is unintended. ??
avoids some unintended behavior from 0 , NaN and "" being treated as falsy values.
We owe a large thanks to community members Wenlu Wang and Titian Cernicova Dragomir for implement-
ing this feature! For more details, check out their pull request and the nullish coalescing proposal repository.
Assertion Functions
Playground
There's a specific set of functions that throw an error if something unexpected happened. They're called
"assertion" functions. As an example, Node.js has a dedicated function for this called assert .
In this example if someValue isn't equal to 42 , then assert will throw an AssertionError .
Assertions in JavaScript are often used to guard against improper types being passed in. For example,
function multiply(x, y) {
assert(typeof x === "number");
assert(typeof y === "number");
return x * y;
}
Unfortunately in TypeScript these checks could never be properly encoded. For loosely-typed code this
meant TypeScript was checking less, and for slightly conservative code it often forced users to use type
assertions.
632
TypeScript 3.7
function yell(str) {
assert(typeof str === "string");
return str.toUppercase();
// Oops! We misspelled 'toUpperCase'.
// Would be great if TypeScript still caught this!
}
The alternative was to instead rewrite the code so that the language could analyze it, but this isn't
convenient.
function yell(str) {
if (typeof str !== "string") {
throw new TypeError("str should have been a string.");
}
// Error caught!
return str.toUppercase();
}
Ultimately the goal of TypeScript is to type existing JavaScript constructs in the least disruptive way. For
that reason, TypeScript 3.7 introduces a new concept called "assertion signatures" which model these asser-
tion functions.
The first type of assertion signature models the way that Node's assert function works. It ensures that
whatever condition is being checked must be true for the remainder of the containing scope.
asserts condition says that whatever gets passed into the condition parameter must be true if the
assert returns (because otherwise it would throw an error). That means that for the rest of the scope,
that condition must be truthy. As an example, using this assertion function means we do catch our original
yell example.
function yell(str) {
assert(typeof str === "string");
return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}
The other type of assertion signature doesn't check for a condition, but instead tells TypeScript that a spe-
cific variable or property has a different type.
633
typescript
Here asserts val is string ensures that after any call to assertIsString , any variable passed in will
be known to be a string .
return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}
These assertion signatures are very similar to writing type predicate signatures:
And just like type predicate signatures, these assertion signatures are incredibly expressive. We can express
some fairly sophisticated ideas with these.
To read up more about assertion signatures, check out the original pull request.
634
TypeScript 3.7
The intent of any function that returns never is that it never returns. It indicates that an exception was
thrown, a halting error condition occurred, or that the program exited. For example, process.exit(...)
in @types/node is specified to return never .
In order to ensure that a function never potentially returned undefined or effectively returned from all
code paths, TypeScript needed some syntactic signal - either a return or throw at the end of a function.
So users found themselves return -ing their failure functions.
Now when these never -returning functions are called, TypeScript recognizes that they affect the control
flow graph and accounts for them.
As with assertion functions, you can read up more at the same pull request.
Type aliases have always had a limitation in how they could be "recursively" referenced. The reason is that
any use of a type alias needs to be able to substitute itself with whatever it aliases. In some cases, that's
not possible, so the compiler rejects certain recursive aliases like the following:
This is a reasonable restriction because any use of Foo would need to be replaced with Foo which would
need to be replaced with Foo which would need to be replaced with Foo which... well, hopefully you get
the idea! In the end, there isn't a type that makes sense in place of Foo .
This is fairly consistent with how other languages treat type aliases, but it does give rise to some slightly
surprising scenarios for how users leverage the feature. For example, in TypeScript 3.6 and prior, the follow-
ing causes an error.
635
typescript
This is strange because there is technically nothing wrong with any use users could always write what was
effectively the same code by introducing an interface.
Because interfaces (and other object types) introduce a level of indirection and their full structure doesn't
need to be eagerly built out, TypeScript has no problem working with this structure.
But workaround of introducing the interface wasn't intuitive for users. And in principle there really wasn't
anything wrong with the original version of ValueOrArray that used Array directly. If the compiler was a
little bit "lazier" and only calculated the type arguments to Array when necessary, then TypeScript could
express these correctly.
That's exactly what TypeScript 3.7 introduces. At the "top level" of a type alias, TypeScript will defer resolv-
ing type arguments to permit these patterns.
This means that code like the following that was trying to represent JSON...
interface JsonObject {
[property: string]: Json;
}
type Json =
| string
| number
| boolean
| null
| { [property: string]: Json }
| Json[];
This new relaxation also lets us recursively reference type aliases in tuples as well. The following code which
used to error is now valid TypeScript code.
For more information, you can read up on the original pull request.
636
TypeScript 3.7
First of all, they're important because they allow TypeScript to type-check against other projects without re-
checking the original source code. They're also important because they allow TypeScript to interoperate with
existing JavaScript libraries that weren't built with TypeScript in mind. Finally, a benefit that is often under-
appreciated: both TypeScript and JavaScript users can benefit from these files when using editors powered
by TypeScript to get things like better auto-completion.
Unfortunately, declaration didn't work with the allowJs flag which allows mixing TypeScript and
JavaScript input files. This was a frustrating limitation because it meant users couldn't use the declara‐
tion flag when migrating codebases, even if they were JSDoc-annotated. TypeScript 3.7 changes that, and
allows the two options to be used together!
The most impactful outcome of this feature might a bit subtle: with TypeScript 3.7, users can write libraries
in JSDoc annotated JavaScript and support TypeScript users.
The way that this works is that when using allowJs , TypeScript has some best-effort analyses to under-
stand common JavaScript patterns; however, the way that some patterns are expressed in JavaScript don't
necessarily look like their equivalents in TypeScript. When declaration emit is turned on, TypeScript fig-
ures out the best way to transform JSDoc comments and CommonJS exports into valid type declarations
and the like in the output .d.ts files.
module.exports.blurImage = blurImage;
/**
* Produces a blurred image from an input buffer.
*
* @param input {Uint8Array}
* @param width {number}
* @param height {number}
*/
function blurImage(input, width, height) {
const numPixels = width * height * 4;
assert(input.length === numPixels);
const result = new Uint8Array(numPixels);
// TODO
return result;
}
/**
* Produces a blurred image from an input buffer.
*
637
typescript
This can go beyond basic functions with @param tags too, where the following example:
/**
* @callback Job
* @returns {void}
*/
/**
* @callback Job
* @returns {void}
*/
/** Queues work */
export class Worker {
constructor(maxDepth?: number);
started: boolean;
depthLimit: number;
638
TypeScript 3.7
/**
* NOTE: queued jobs may add more items to queue
* @type {Job[]}
*/
queue: Job[];
/**
* Adds a work item to the queue
* @param {Job} work
*/
push(work: Job): void;
/**
* Starts the queue if it has not yet started
*/
start(): boolean;
}
export type Job = () => void;
Note that when using these flags together, TypeScript doesn't necessarily have to downlevel .js files. If
you simply want TypeScript to create .d.ts files, you can use the emitDeclarationOnly compiler option.
For more details, you can check out the original pull request.
class C {
foo = 100;
bar: string;
}
class C {
constructor() {
this.foo = 100;
}
}
Unfortunately, while this seemed to be the direction that the proposal moved towards in its earlier days,
there is an extremely strong chance that public class fields will be standardized differently. Instead, the
original code sample might need to de-sugar to something closer to the following:
class C {
constructor() {
Object.defineProperty(this, "foo", {
enumerable: true,
configurable: true,
writable: true,
value: 100,
});
Object.defineProperty(this, "bar", {
enumerable: true,
639
typescript
configurable: true,
writable: true,
value: void 0,
});
}
}
While TypeScript 3.7 isn't changing any existing emit by default, we've been rolling out changes incremen-
tally to help users mitigate potential future breakage. We've provided a new flag called useDefineFor‐
ClassFields to enable this emit mode with some new checking logic.
This can cause quite a bit of fallout for existing code that use inheritance. First of all, set accessors from
base classes won't get triggered - they'll be completely overwritten.
class Base {
set data(value: string) {
console.log("data changed to " + value);
}
}
Secondly, using class fields to specialize properties from base classes also won't work.
interface Animal {
animalStuff: any;
}
interface Dog extends Animal {
dogStuff: any;
}
class AnimalHouse {
resident: Animal;
constructor(animal: Animal) {
this.resident = animal;
}
}
constructor(dog: Dog) {
super(dog);
}
}
640
TypeScript 3.7
What these two boil down to is that mixing properties with accessors is going to cause issues, and so will
re-declaring properties with no initializers.
To detect the issue around accessors, TypeScript 3.7 will now emit get / set accessors in .d.ts files so
that in TypeScript can check for overridden accessors.
Code that's impacted by the class fields change can get around the issue by converting field initializers to
assignments in constructor bodies.
class Base {
set data(value: string) {
console.log("data changed to " + value);
}
}
To help mitigate the second issue, you can either add an explicit initializer or add a declare modifier to in-
dicate that a property should have no emit.
interface Animal {
animalStuff: any;
}
interface Dog extends Animal {
dogStuff: any;
}
class AnimalHouse {
resident: Animal;
constructor(animal: Animal) {
this.resident = animal;
}
}
constructor(dog: Dog) {
super(dog);
}
}
Currently useDefineForClassFields is only available when targeting ES5 and upwards, since
Object.defineProperty doesn't exist in ES3. To achieve similar checking for issues, you can create a sep-
arate project that targets ES5 and uses noEmit to avoid a full build.
For more information, you can take a look at the original pull request for these changes.
641
typescript
We strongly encourage users to try the useDefineForClassFields flag and report back on our issue track-
er or in the comments below. This includes feedback on difficulty of adopting the flag so we can understand
how we can make migration easier.
In TypeScript 3.7, when opening a project with dependencies, TypeScript will automatically use the source
.ts / .tsx files instead. This means projects using project references will now see an improved editing ex-
perience where semantic operations are up-to-date and "just work". You can disable this behavior with the
compiler option disableSourceOfProjectReferenceRedirect which may be appropriate when working in
very large projects where this change may impact editing performance.
You can read up more about this change by reading up on its pull request.
interface User {
isAdministrator(): boolean;
notify(): void;
doNotDisturb?(): boolean;
}
// later...
Here, we forgot to call isAdministrator , and the code incorrectly allows non-adminstrator users to edit
the configuration!
642
TypeScript 3.7
This check is a breaking change, but for that reason the checks are very conservative. This error is only is-
sued in if conditions, and it is not issued on optional properties, if strictNullChecks is off, or if the
function is later called within the body of the if :
interface User {
isAdministrator(): boolean;
notify(): void;
doNotDisturb?(): boolean;
}
If you intended to test the function without calling it, you can correct the definition of it to include unde‐
fined / null , or use !! to write something like if (!!user.isAdministrator) to indicate that the coer-
cion is intentional.
We owe a big thanks to GitHub user @jwbay who took the initiative to create a proof-of-concept and iterat-
ed to provide us with the current version.
643
typescript
Choosing a value of "insert" or "remove" also affects the format of auto-imports, extracted types, and other
generated code provided by TypeScript services. Leaving the setting on its default value of "ignore" makes
generated code match the semicolon preference detected in the current file.
While not a breakage per se, opting in to the useDefineForClassFields flag can cause breakage when:
644
TypeScript 3.7
To understand the full impact, read the section above on the useDefineForClassFields flag.
// ./someOtherModule.ts
interface SomeType {
y: string;
}
// ./myModule.ts
import { SomeType } from "./someOtherModule";
export interface SomeType {
x: number;
}
Here, SomeType appears to originate in both the import declaration and the local interface declaration.
Perhaps surprisingly, inside the module, SomeType refers exclusively to the import ed definition, and the
local declaration SomeType is only usable when imported from another file. This is very confusing and our
review of the very small number of cases of code like this in the wild showed that developers usually
thought something different was happening.
In TypeScript 3.7, this is now correctly identified as a duplicate identifier error. The correct fix depends on
the original intent of the author and should be addressed on a case-by-case basis. Usually, the naming con-
flict is unintentional and the best fix is to rename the imported type. If the intent was to augment the im-
ported type, a proper module augmentation should be written instead.
Go to TOC
645
typescript
TypeScript 3.8 adds a new syntax for type-only imports and exports.
import type only imports declarations to be used for type annotations and declarations. It always gets
fully erased, so there's no remnant of it at runtime. Similarly, export type only provides an export that
can be used for type contexts, and is also erased from TypeScript's output.
It's important to note that classes have a value at runtime and a type at design-time, and the use is
context-sensitive. When using import type to import a class, you can't do things like extend from it.
interface ButtonProps {
// ...
}
If you've used Flow before, the syntax is fairly similar. One difference is that we've added a few restrictions
to avoid code that might appear ambiguous.
In conjunction with import type , TypeScript 3.8 also adds a new compiler flag to control what happens
with imports that won't be utilized at runtime: importsNotUsedAsValues . This flag takes 3 different
values:
remove : this is today's behavior of dropping these imports. It's going to continue to be the default, and
is a non-breaking change.
preserve : this preserves all imports whose values are never used. This can cause imports/side-effects
to be preserved.
646
TypeScript 3.8
error : this preserves all imports (the same as the preserve option), but will error when a value im-
port is only used as a type. This might be useful if you want to ensure no values are being accidentally
imported, but still make side-effect imports explicit.
For more information about the feature, you can take a look at the pull request, and relevant changes
around broadening where imports from an import type declaration can be used.
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
greet() {
console.log(`Hello, my name is ${this.#name}!`);
}
}
jeremy.#name;
// ~~~~~
// Property '#name' is not accessible outside class 'Person'
// because it has a private identifier.
Unlike regular properties (even ones declared with the private modifier), private fields have a few rules to
keep in mind. Some of them are:
Private fields start with a # character. Sometimes we call these private names.
Every private field name is uniquely scoped to its containing class.
TypeScript accessibility modifiers like public or private can't be used on private fields.
Private fields can't be accessed or even detected outside of the containing class - even by JS users!
Sometimes we call this hard privacy.
Apart from "hard" privacy, another benefit of private fields is that uniqueness we just mentioned. For exam-
ple, regular property declarations are prone to being overwritten in subclasses.
class C {
foo = 10;
cHelper() {
return this.foo;
}
}
class D extends C {
foo = 20;
dHelper() {
return this.foo;
647
typescript
}
}
With private fields, you'll never have to worry about this, since each field name is unique to the containing
class.
class C {
#foo = 10;
cHelper() {
return this.#foo;
}
}
class D extends C {
#foo = 20;
dHelper() {
return this.#foo;
}
}
Another thing worth noting is that accessing a private field on any other type will result in a TypeError !
class Square {
#sideLength: number;
constructor(sideLength: number) {
this.#sideLength = sideLength;
}
equals(other: any) {
return this.#sideLength === other.#sideLength;
}
}
// Boom!
// TypeError: attempted to get private field on non-instance
// This fails because 'b' is not an instance of 'Square'.
console.log(a.equals(b));
Finally, for any plain .js file users, private fields always have to be declared before they're assigned to.
class C {
// No declaration for '#foo'
// :(
648
TypeScript 3.8
constructor(foo: number) {
// SyntaxError!
// '#foo' needs to be declared before writing to it.
this.#foo = foo;
}
}
JavaScript has always allowed users to access undeclared properties, whereas TypeScript has always re-
quired declarations for class properties. With private fields, declarations are always needed regardless of
whether we're working in .js or .ts files.
class C {
/** @type {number} */
#foo;
constructor(foo: number) {
// This works.
this.#foo = foo;
}
}
For more information about the implementation, you can check out the original pull request
When it comes to properties, TypeScript's private modifiers are fully erased - that means that at runtime,
it acts entirely like a normal property and there's no way to tell that it was declared with a private modifi-
er. When using the private keyword, privacy is only enforced at compile-time/design-time, and for
JavaScript consumers it's entirely intent-based.
class C {
private foo = 10;
}
The upside is that this sort of "soft privacy" can help your consumers temporarily work around not having
access to some API, and also works in any runtime.
On the other hand, ECMAScript's # privates are completely inaccessible outside of the class.
649
typescript
class C {
#foo = 10;
}
This hard privacy is really useful for strictly ensuring that nobody can take use of any of your internals. If
you're a library author, removing or renaming a private field should never cause a breaking change.
As we mentioned, another benefit is that subclassing can be easier with ECMAScript's # privates because
they really are private. When using ECMAScript # private fields, no subclass ever has to worry about colli-
sions in field naming. When it comes to TypeScript's private property declarations, users still have to be
careful not to trample over properties declared in superclasses.
One more thing to think about is where you intend for your code to run. TypeScript currently can't support
this feature unless targeting ECMAScript 2015 (ES6) targets or higher. This is because our downleveled im-
plementation uses WeakMap s to enforce privacy, and WeakMap s can't be polyfilled in a way that doesn't
cause memory leaks. In contrast, TypeScript's private -declared properties work with all targets - even
ECMAScript 3!
A final consideration might be speed: private properties are no different from any other property, so ac-
cessing them is as fast as any other property access no matter which runtime you target. In contrast, be-
cause # private fields are downleveled using WeakMap s, they may be slower to use. While some runtimes
might optimize their actual implementations of # private fields, and even have speedy WeakMap imple-
mentations, that might not be the case in all runtimes.
export * as ns Syntax
It's often common to have a single entry-point that exposes all the members of another module as a single
member.
This is so common that ECMAScript 2020 recently added a new syntax to support this pattern!
This is a nice quality-of-life improvement to JavaScript, and TypeScript 3.8 implements this syntax. When
your module target is earlier than es2020 , TypeScript will output something along the lines of the first code
snippet.
650
TypeScript 3.8
Top-Level await
TypeScript 3.8 provides support for a handy upcoming ECMAScript feature called "top-level await ".
JavaScript users often introduce an async function in order to use await , and then immediately called the
function after defining it.
This is because previously in JavaScript (along with most other languages with a similar feature), await
was only allowed within the body of an async function. However, with top-level await , we can use await
at the top level of a module.
Note there's a subtlety: top-level await only works at the top level of a module, and files are only consid-
ered modules when TypeScript finds an import or an export . In some basic cases, you might need to
write out export {} as some boilerplate to make sure of this.
Top level await may not work in all environments where you might expect at this point. Currently, you can
only use top level await when the target compiler option is es2017 or above, and module is esnext
or system . Support within several environments and bundlers may be limited or may require enabling ex-
perimental support.
For more information on our implementation, you can check out the original pull request.
651
typescript
Because JavaScript files don't have dedicated syntax for type-checking, TypeScript leverages JSDoc.
TypeScript 3.8 understands a few new JSDoc tags for properties.
First are the accessibility modifiers: @public , @private , and @protected . These tags work exactly like
public , private , and protected respectively work in TypeScript.
// @ts-check
class Foo {
constructor() {
/** @private */
this.stuff = 100;
}
printStuff() {
console.log(this.stuff);
}
}
new Foo().stuff;
// ~~~~~
// error! Property 'stuff' is private and only accessible within class 'Foo'.
@public is always implied and can be left off, but means that a property can be reached from
anywhere.
@private means that a property can only be used within the containing class.
@protected means that a property can only be used within the containing class, and all derived sub-
classes, but not on dissimilar instances of the containing class.
Next, we've also added the @readonly modifier to ensure that a property is only ever written to during
initialization.
// @ts-check
class Foo {
constructor() {
/** @readonly */
this.stuff = 100;
}
writeToStuff() {
this.stuff = 200;
// ~~~~~
// Cannot assign to 'stuff' because it is a read-only property.
}
}
new Foo().stuff++;
// ~~~~~
// Cannot assign to 'stuff' because it is a read-only property.
652
TypeScript 3.8
For some context, on operating systems like Linux, TypeScript installs directory watchers (as opposed to file
watchers) on node_modules and many of its subdirectories to detect changes in dependencies. This is be-
cause the number of available file watchers is often eclipsed by the of files in node_modules , whereas
there are way fewer directories to track.
Older versions of TypeScript would immediately install directory watchers on folders, and at startup that
would be fine; however, during an npm install, a lot of activity will take place within node_modules and
that can overwhelm TypeScript, often slowing editor sessions to a crawl. To prevent this, TypeScript 3.8
waits slightly before installing directory watchers to give these highly volatile directories some time to
stabilize.
Because every project might work better under different strategies, and this new approach might not work
well for your workflows, TypeScript 3.8 introduces a new watchOptions field in tsconfig.json and js‐
config.json which allows users to tell the compiler/language service which watching strategies should be
used to keep track of files and directories.
{
// Some typical compiler options
"compilerOptions": {
"target": "es2020",
"moduleResolution": "node"
// ...
},
watchFile : the strategy for how individual files are watched. This can be set to
fixedPollingInterval : Check every file for changes several times a second at a fixed interval.
priorityPollingInterval : Check every file for changes several times a second, but use heuristics
to check certain types of files less frequently than others.
dynamicPriorityPolling : Use a dynamic queue where less-frequently modified files will be checked
less often.
useFsEvents (the default): Attempt to use the operating system/file system's native events for file
changes.
useFsEventsOnParentDirectory : Attempt to use the operating system/file system's native events
to listen for changes on a file's containing directories. This can use fewer file watchers, but might be
less accurate.
653
typescript
watchDirectory : the strategy for how entire directory trees are watched under systems that lack re-
cursive file-watching functionality. This can be set to:
fixedPollingInterval : Check every directory for changes several times a second at a fixed
interval.
dynamicPriorityPolling : Use a dynamic queue where less-frequently modified directories will be
checked less often.
useFsEvents (the default): Attempt to use the operating system/file system's native events for di-
rectory changes.
fallbackPolling : when using file system events, this option specifies the polling strategy that gets
used when the system runs out of native file watchers and/or doesn't support native file watchers. This
can be set to
For more information on these changes, head over to GitHub to see the pull request to read more.
For example, consider a file fileD.ts that imports fileC.ts that imports fileB.ts that imports file‐
A.ts as follows:
In --watch mode, a change in fileA.ts would typically mean that TypeScript would need to at least re-
check fileB.ts , fileC.ts , and fileD.ts . Under assumeChangesOnlyAffectDirectDependencies , a
change in fileA.ts means that only fileA.ts and fileB.ts need to be re-checked.
In a codebase like Visual Studio Code, this reduced rebuild times for changes in certain files from about 14
seconds to about 1 second. While we don't necessarily recommend this option for all codebases, you might
be interested if you have an extremely large codebase and are willing to defer full project errors until later
(e.g. a dedicated build via a tsconfig.fullbuild.json or in CI).
For more details, you can see the original pull request.
Go to TOC
654
TypeScript 3.9
interface Lion {
roar(): void;
}
interface Seal {
singKissFromARose(): void;
}
This is strange behavior! The fact that sealExhibit contained an undefined somehow poisoned type of
lion to include undefined .
Thanks to a pull request from Jack Bates, this has been fixed with improvements in our inference process in
TypeScript 3.9. The above no longer errors. If you've been stuck on older versions of TypeScript due to is-
sues around Promise s, we encourage you to give 3.9 a shot!
We initially anticipated shipping awaited in TypeScript 3.9, but as we've run early TypeScript builds with
existing codebases, we've realized that the feature needs more design work before we can roll it out to
everyone smoothly. As a result, we've decided to pull the feature out of our main branch until we feel more
confident. We'll be experimenting more with the feature, but we won't be shipping it as part of this release.
Speed Improvements
TypeScript 3.9 ships with many new speed improvements. Our team has been focusing on performance af-
ter observing extremely poor editing/compilation speed with packages like material-ui and styled-compo-
nents. We've dived deep here, with a series of different pull requests that optimize certain pathological cas-
es involving large unions, intersections, conditional types, and mapped types.
https://github.com/microsoft/TypeScript/pull/36576
655
typescript
https://github.com/microsoft/TypeScript/pull/36590
https://github.com/microsoft/TypeScript/pull/36607
https://github.com/microsoft/TypeScript/pull/36622
https://github.com/microsoft/TypeScript/pull/36754
https://github.com/microsoft/TypeScript/pull/36696
Each of these pull requests gains about a 5-10% reduction in compile times on certain codebases. In total,
we believe we've achieved around a 40% reduction in material-ui's compile time!
We also have some changes to file renaming functionality in editor scenarios. We heard from the Visual
Studio Code team that when renaming a file, just figuring out which import statements needed to be updat-
ed could take between 5 to 10 seconds. TypeScript 3.9 addresses this issue by changing the internals of
how the compiler and language service caches file lookups.
While there's still room for improvement, we hope this work translates to a snappier experience for
everyone!
// @ts-expect-error Comments
Imagine that we're writing a library in TypeScript and we're exporting some function called doStuff as
part of our public API. The function's types declare that it takes two string s so that other TypeScript users
can get type-checking errors, but it also does a runtime check (maybe only in development builds) to give
JavaScript users a helpful error.
// do some stuff
}
So TypeScript users will get a helpful red squiggle and an error message when they misuse this function,
and JavaScript users will get an assertion error. We'd like to test this behavior, so we'll write a unit test.
expect(() => {
doStuff(123, 456);
}).toThrow();
Unfortunately if our tests are written in TypeScript, TypeScript will give us an error!
doStuff(123, 456);
// ~~~
// error: Type 'number' is not assignable to type 'string'.
That's why TypeScript 3.9 brings a new feature: // @ts-expect-error comments. When a line is preced-
ed by a // @ts-expect-error comment, TypeScript will suppress that error from being reported; but if
there's no error, TypeScript will report that // @ts-expect-error wasn't necessary.
656
TypeScript 3.9
// @ts-expect-error
console.log(47 * "octopus");
// @ts-expect-error
console.log(1 + 1);
We'd like to extend a big thanks to Josh Goldberg, the contributor who implemented this feature. For more
information, you can take a look at the ts-expect-error pull request.
ts-ignore or ts-expect-error ?
In some ways // @ts-expect-error can act as a suppression comment, similar to // @ts-ignore . The
difference is that // @ts-ignore will do nothing if the following line is error-free.
You might be tempted to switch existing // @ts-ignore comments over to // @ts-expect-error , and
you might be wondering which is appropriate for future code. While it's entirely up to you and your team,
we have some ideas of which to pick in certain situations.
you're writing test code where you actually want the type system to error on an operation
you expect a fix to be coming in fairly quickly and you just need a quick workaround
you're in a reasonably-sized project with a proactive team that wants to remove suppression comments
as soon affected code is valid again
you have a larger project and new errors have appeared in code with no clear owner
you are in the middle of an upgrade between two different versions of TypeScript, and a line of code er-
rors in one version but not another.
you honestly don't have the time to decide which of these options is better.
// Oops!
if (hasImportantPermissions) {
// ~~~~~~~~~~~~~~~~~~~~~~~
// This condition will always return true since the function is always defined.
657
typescript
However, this error only applied to conditions in if statements. Thanks to a pull request from Alexander
Tarasyuk, this feature is also now supported in ternary conditionals (i.e. the cond ? trueExpr : falseEx‐
pr syntax).
https://github.com/microsoft/TypeScript/issues/36048
Editor Improvements
The TypeScript compiler not only powers the TypeScript editing experience in most major editors, it also
powers the JavaScript experience in the Visual Studio family of editors and more. Using new
TypeScript/JavaScript functionality in your editor will differ depending on your editor, but
Visual Studio Code supports selecting different versions of TypeScript. Alternatively, there's the
JavaScript/TypeScript Nightly Extension to stay on the bleeding edge (which is typically very stable).
Visual Studio 2017/2019 have [the SDK installers above] and MSBuild installs.
Sublime Text 3 supports selecting different versions of TypeScript
In older versions, TypeScript always assumed that regardless of your file, you wanted an ECMAScript-style
import like
However, not everyone is targeting ECMAScript-style modules when writing JavaScript files. Plenty of users
still use CommonJS-style require(...) imports like so
const fs = require("fs");
658
TypeScript 3.9
TypeScript now automatically detects the types of imports you're using to keep your file's style clean and
consistent.
For more details on the change, see the corresponding pull request.
/*start*/
for (let i = 0; i <= maxValue; i++) {
// First get the squared value.
let square = i ** 2;
If we highlighted the range from /*start*/ to /*end*/ in our editor to extract to a new function, we'd
end up with code like the following.
printSquares();
function printSquares() {
for (let i = 0; i <= maxValue; i++) {
// First get the squared value.
let square = i ** 2;
// Now print the squared value.
console.log(square);
}
}
659
typescript
Extracting the for loop to a function in older versions of TypeScript. A newline is not preserved.
That's not ideal - we had a blank line between each statement in our for loop, but the refactoring got rid
of it! TypeScript 3.9 does a little more work to preserve what we write.
printSquares();
function printSquares() {
for (let i = 0; i <= maxValue; i++) {
// First get the squared value.
let square = i ** 2;
You can see more about the implementation in this pull request
660
TypeScript 3.9
// before
let f1 = () => 42;
Thanks to a pull request from community member Wenlu Wang, TypeScript can provide a quick-fix to add
missing return statements, remove curly braces, or add parentheses to arrow function bodies that look
suspiciously like object literals.
TypeScript fixing an error where no expression is returned by adding a `return` statement or removing
curly braces.
One case where this slightly fell over is when a tsconfig.json simply existed to reference other tscon‐
fig.json files.
661
typescript
// tsconfig.json
{
"files": [],
"references": [
{ "path": "./tsconfig.shared.json" },
{ "path": "./tsconfig.frontend.json" },
{ "path": "./tsconfig.backend.json" }
]
}
This file that really does nothing but manage other project files is often called a "solution" in some environ-
ments. Here, none of these tsconfig.*.json files get picked up by the server, but we'd really like the lan-
guage server to understand that the current .ts file probably belongs to one of the mentioned projects in
this root tsconfig.json .
TypeScript 3.9 adds support to editing scenarios for this configuration. For more details, take a look at the
pull request that added this functionality.
Breaking Changes
Parsing Differences in Optional Chaining and Non-Null Assertions
TypeScript recently implemented the optional chaining operator, but we've received user feedback that the
behavior of optional chaining ( ?. ) with the non-null assertion operator ( ! ) is extremely counter-intuitive.
foo?.bar!.baz;
(foo?.bar).baz;
In the above code the parentheses stop the "short-circuiting" behavior of optional chaining, so if foo is
undefined , accessing baz will cause a runtime error.
The Babel team who pointed this behavior out, and most users who provided feedback to us, believe that
this behavior is wrong. We do too! The thing we heard the most was that the ! operator should just "dis-
appear" since the intent was to remove null and undefined from the type of bar .
In other words, most people felt that the original snippet should be interpreted as
foo?.bar.baz;
This is a breaking change, but we believe most code was written with the new interpretation in mind. Users
who want to revert to the old behavior can add explicit parentheses around the left side of the ! operator.
foo?.bar!.baz;
662
TypeScript 3.9
Luckily, thanks to the pull request enforcing this from Brad Zacher, you'll get an error message along the
lines of
For example:
let directions = <span>Navigate to: Menu Bar > Tools > Options</span>;
// ~ ~
// Unexpected token. Did you mean `{'>'}` or `>`?
That error message came with a handy quick fix, and thanks to Alexander Tarasyuk, you can apply these
changes in bulk if you have a lot of errors.
interface A {
a: number; // notice this is 'number'
}
interface B {
b: string;
}
interface C {
a?: boolean; // notice this is 'boolean'
b: string;
}
y = x;
In previous versions of TypeScript, this was allowed because while A was totally incompatible with C , B
was compatible with C .
In TypeScript 3.9, so long as every type in an intersection is a concrete object type, the type system will
consider all of the properties at once. As a result, TypeScript will see that the a property of A & B is in-
compatible with that of C :
663
typescript
For more information on this change, see the corresponding pull request.
interface Circle {
kind: "circle";
radius: number;
}
interface Square {
kind: "square";
sideLength: number;
}
This code is slightly weird because there's really no way to create an intersection of a Circle and a
Square - they have two incompatible kind fields. In previous versions of TypeScript, this code was al-
lowed and the type of kind itself was never because "circle" & "square" described a set of values
that could never exist.
In TypeScript 3.9, the type system is more aggressive here - it notices that it's impossible to intersect
Circle and Square because of their kind properties. So instead of collapsing the type of z.kind to
never , it collapses the type of z itself ( Circle & Square ) to never . That means the above code now
errors with:
Most of the breaks we observed seem to correspond with slightly incorrect type declarations. For more de-
tails, see the original pull request.
Thanks to a pull request from GitHub user pathurs, TypeScript 3.9 now conforms more closely with
ECMAScript in this regard.
664
TypeScript 3.9
This was an oversight, so TypeScript 3.9 takes a more conservative approach and issues an error on these
questionable operations.
Adding this file to an ambient *.d.ts to your project will bring them back:
interface AudioTrackList {
[Symbol.iterator](): IterableIterator<AudioTrack>;
}
interface HTMLVideoElement {
readonly audioTracks: AudioTrackList
msHorizontalMirror: boolean;
readonly msIsLayoutOptimalForPlayback: boolean;
readonly msIsStereo3D: boolean;
msStereo3DPackingMode: string;
msStereo3DRenderMode: string;
msZoom: boolean;
665
typescript
interface MediaError {
readonly msExtendedCode: number;
readonly MS_MEDIA_ERR_ENCRYPTED: number;
}
Go to TOC
666
TypeScript 4.0
Also consider tail , that takes an array or tuple, and returns all elements but the first.
function tail(arg) {
const [_, ...result] = arg;
return result;
}
For concat , the only valid thing we could do in older versions of the language was to try and write some
overloads.
Uh...okay, that's...seven overloads for when the second array is always empty. Let's add some for when
arr2 has one argument.
We hope it's clear that this is getting unreasonable. Unfortunately, you'd also end up with the same sorts of
issues typing a function like tail .
This is another case of what we like to call "death by a thousand overloads", and it doesn't even solve the
problem generally. It only gives correct types for as many overloads as we care to write. If we wanted to
make a catch-all case, we'd need an overload like the following:
667
typescript
But that signature doesn't encode anything about the lengths of the input, or the order of the elements,
when using tuples.
TypeScript 4.0 brings two fundamental changes, along with inference improvements, to make typing these
possible.
The first change is that spreads in tuple type syntax can now be generic. This means that we can represent
higher-order operations on tuples and arrays even when we don't know the actual types we're operating
over. When generic spreads are instantiated (or, replaced with a real type) in these tuple types, they can
produce other sets of array and tuple types.
For example, that means we can type function like tail , without our "death by a thousand overloads"
issue.
const r1 = tail(myTuple);
// ^?
The second change is that rest elements can occur anywhere in a tuple - not just at the end!
Note that in cases when we spread in a type without a known length, the resulting type becomes unbound-
ed as well, and all the following elements factor into the resulting rest element type.
By combining both of these behaviors together, we can write a single well-typed signature for concat :
668
TypeScript 4.0
function concat<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U] {
return [...arr1, ...arr2];
}
While that one signature is still a bit lengthy, it's just one signature that doesn't have to be repeated, and it
gives predictable behavior on all arrays and tuples.
This functionality on its own is great, but it shines in more sophisticated scenarios too. For example, consid-
er a function to partially apply arguments called partialCall . partialCall takes a function - let's call it
f - along with the initial few arguments that f expects. It then returns a new function that takes any oth-
er arguments that f still needs, and calls f when it receives them.
TypeScript 4.0 improves the inference process for rest parameters and rest tuple elements so that we can
type this and have it "just work".
In this case, partialCall understands which parameters it can and can't initially take, and returns func-
tions that appropriately accept and reject anything left over.
// This works!
const f3 = partialCall(foo, "hello");
// ^?
// Works!
f3(123, true);
669
typescript
f3();
f3(123, "hello");
Variadic tuple types enable a lot of new exciting patterns, especially around function composition. We ex-
pect we may be able to leverage it to do a better job type-checking JavaScript's built-in bind method. A
handful of other inference improvements and patterns also went into this, and if you're interested in learn-
ing more, you can take a look at the pull request for variadic tuples.
For example, the following function that uses a tuple type as a rest parameter...
// @errors: 2554
function foo(arg0: string, arg1: number): void {
// ...
}
// ---cut---
foo("hello", 42);
There is one place where the differences begin to become observable though: readability. In the first exam-
ple, we have no parameter names for the first and second elements. While these have no impact on type-
checking, the lack of labels on tuple positions can make them harder to use - harder to communicate our
intent.
That's why in TypeScript 4.0, tuples types can now provide labels.
To deepen the connection between parameter lists and tuple types, the syntax for rest elements and option-
al elements mirrors the syntax for parameter lists.
670
TypeScript 4.0
There are a few rules when using labeled tuples. For one, when labeling a tuple element, all other elements
in the tuple must also be labeled.
// @errors: 5084
type Bar = [first: string, number];
It's worth noting - labels don't require us to name our variables differently when destructuring. They're
purely there for documentation and tooling.
Overall, labeled tuples are handy when taking advantage of patterns around tuples and argument lists,
along with implementing overloads in a type-safe way. In fact, TypeScript's editor support will try to display
them as overloads when possible.
Signature help displaying a union of labeled tuples as in a parameter list as two signatures
To learn more, check out the pull request for labeled tuple elements.
class Square {
// Previously both of these were any
area;
// ^?
sideLength;
// ^?
671
typescript
constructor(sideLength: number) {
this.sideLength = sideLength;
this.area = sideLength ** 2;
}
}
In cases where not all paths of a constructor assign to an instance member, the property is considered to
potentially be undefined .
// @errors: 2532
class Square {
sideLength;
// ^?
constructor(sideLength: number) {
if (Math.random()) {
this.sideLength = sideLength;
}
}
get area() {
return this.sideLength ** 2;
}
}
In cases where you know better (e.g. you have an initialize method of some sort), you'll still need an
explicit type annotation along with a definite assignment assertion ( ! ) if you're in strictPropertyIni‐
tialization .
class Square {
// definite assignment assertion
// v
sideLength!: number;
// ^^^^^^^^
// type annotation
constructor(sideLength: number) {
this.initialize(sideLength);
}
initialize(sideLength: number) {
this.sideLength = sideLength;
}
get area() {
return this.sideLength ** 2;
}
}
672
TypeScript 4.0
// Addition
// a = a + b
a += b;
// Subtraction
// a = a - b
a -= b;
// Multiplication
// a = a * b
a *= b;
// Division
// a = a / b
a /= b;
// Exponentiation
// a = a ** b
a **= b;
So many operators in JavaScript have a corresponding assignment operator! Up until recently, however,
there were three notable exceptions: logical and ( && ), logical or ( || ), and nullish coalescing ( ?? ).
That's why TypeScript 4.0 supports a new ECMAScript feature to add three new assignment operators:
&&= , ||= , and ??= .
These operators are great for substituting any example where a user might write code like the following:
a = a && b;
a = a || b;
a = a ?? b;
There are even some patterns we've seen (or, uh, written ourselves) to lazily initialize values, only if they'll
be needed.
// After
(values ??= []).push("hello");
673
typescript
On the rare case that you use getters or setters with side-effects, it's worth noting that these operators only
perform assignments if necessary. In that sense, not only is the right side of the operator "short-circuited" -
the assignment itself is too.
if (!obj.prop) {
obj.prop = foo();
}
Try running the following example to see how that differs from always performing the assignment.
const obj = {
get prop() {
console.log("getter has run");
// Replace me!
return Math.random() < 0.5;
},
set prop(_val: boolean) {
console.log("setter has run");
}
};
function foo() {
console.log("right side evaluated");
return true;
}
We'd like to extend a big thanks to community member Wenlu Wang for this contribution!
For more details, you can take a look at the pull request here. You can also check out TC39's proposal
repository for this feature.
// @useUnknownInCatchVariables: false
try {
// Do some work
} catch (x) {
// x has type 'any' - have fun!
console.log(x.message);
console.log(x.toUpperCase());
674
TypeScript 4.0
x++;
x.yadda.yadda.yadda();
}
The above has some undesirable behavior if we're trying to prevent more errors from happening in our
error-handling code! Because these variables have the type any by default, they lack any type-safety
which could have errored on invalid operations.
That's why TypeScript 4.0 now lets you specify the type of catch clause variables as unknown instead.
unknown is safer than any because it reminds us that we need to perform some sorts of type-checks be-
fore operating on our values.
// @errors: 2571
try {
// ...
} catch (e: unknown) {
// Can't access values on unknowns
console.log(e.toUpperCase());
While the types of catch variables won't change by default, we might consider a new strict mode flag in
the future so that users can opt in to this behavior. In the meantime, it should be possible to write a lint
rule to force catch variables to have an explicit annotation of either : any or : unknown .
For more details you can peek at the changes for this feature.
In TypeScript 4.0, users can customize the fragment factory through the new jsxFragmentFactory option.
As an example, the following tsconfig.json file tells TypeScript to transform JSX in a way compatible
with React, but switches each factory invocation to h instead of React.createElement , and uses
Fragment instead of React.Fragment .
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"jsx": "react",
"jsxFactory": "h",
"jsxFragmentFactory": "Fragment"
}
}
675
typescript
In cases where you need to have a different JSX factory on a per-file basis, you can take advantage of the
new /** @jsxFrag */ pragma comment. For example, the following...
// @noErrors
// Note: these pragma comments need to be written
// with a JSDoc-style multiline syntax to take effect.
/** @jsx h */
/** @jsxFrag Fragment */
// @noErrors
// @showEmit
// Note: these pragma comments need to be written
// with a JSDoc-style multiline syntax to take effect.
/** @jsx h */
/** @jsxFrag Fragment */
We'd like to extend a big thanks to community member Noj Vek for sending this pull request and patiently
working with our team on it.
You can see that the pull request for more details!
TypeScript 4.0 changes this which gives a great speed boost in these scenarios, and in turn improves --
build mode scenarios (which imply both incremental and noEmitOnError ).
676
TypeScript 4.0
For more details, you can see the implementing pull request.
Editor Improvements
The TypeScript compiler doesn't only power the editing experience for TypeScript itself in most major edi-
tors - it also powers the JavaScript experience in the Visual Studio family of editors and more. For that rea-
son, much of our work focuses on improving editor scenarios - the place you spend most of your time as a
developer.
Using new TypeScript/JavaScript functionality in your editor will differ depending on your editor, but
Visual Studio Code supports selecting different versions of TypeScript. Alternatively, there's the
JavaScript/TypeScript Nightly Extension to stay on the bleeding edge (which is typically very stable).
Visual Studio 2017/2019 have [the SDK installers above] and MSBuild installs.
Sublime Text 3 supports selecting different versions of TypeScript
You can check out a partial list of editors that have support for TypeScript to learn more about whether your
favorite editor has support to use new versions.
677
typescript
Keep in mind that while this refactoring doesn't perfectly capture the same behavior due to subtleties with
truthiness/falsiness in JavaScript, we believe it should capture the intent for most use-cases, especially
when TypeScript has more precise knowledge of your types.
For more details, check out the pull request for this feature.
This new functionality is available thanks to Wenlu Wang. See the pull request for more details.
678
TypeScript 4.0
That's why we've been working on a new mode for editors to provide a partial experience until the full lan-
guage service experience has loaded up. The core idea is that editors can run a lightweight partial server
that only looks at the current files that the editor has open.
It's hard to say precisely what sorts of improvements you'll see, but anecdotally, it used to take anywhere
between 20 seconds to a minute before TypeScript would become fully responsive on the Visual Studio Code
codebase. In contrast, our new partial semantic mode seems to bring that delay down to just a few
seconds. As an example, in the following video, you can see two side-by-side editors with TypeScript 3.9
running on the left and TypeScript 4.0 running on the right.
When restarting both editors on a particularly large codebase, the one with TypeScript 3.9 can't provide
completions or quick info at all. On the other hand, the editor with TypeScript 4.0 can immediately give us a
rich experience in the current file we're editing, despite loading the full project in the background.
Currently the only editor that supports this mode is Visual Studio Code which has some UX improvements
coming up in Visual Studio Code Insiders. We recognize that this experience may still have room for polish
in UX and functionality, and we have a list of improvements in mind. We're looking for more feedback on
what you think might be useful.
For more information, you can see the original proposal, the implementing pull request, along with the fol-
low-up meta issue.
Smarter Auto-Imports
Auto-import is a fantastic feature that makes coding a lot easier; however, every time auto-import doesn't
seem to work, it can throw users off a lot. One specific issue that we heard from users was that auto-im-
ports didn't work on dependencies that were written in TypeScript - that is, until they wrote at least one ex-
plicit import somewhere else in their project.
679
typescript
Why would auto-imports work for @types packages, but not for packages that ship their own types? It
turns out that auto-imports only work on packages your project already includes. Because TypeScript has
some quirky defaults that automatically add packages in node_modules/@types to your project, those
packages would be auto-imported. On the other hand, other packages were excluded because crawling
through all your node_modules packages can be really expensive.
All of this leads to a pretty lousy getting started experience for when you're trying to auto-import something
that you've just installed but haven't used yet.
TypeScript 4.0 now does a little extra work in editor scenarios to include the packages you've listed in your
package.json 's dependencies (and peerDependencies ) fields. The information from these packages is
only used to improve auto-imports, and doesn't change anything else like type-checking. This allows us to
provide auto-imports for all of your dependencies that have types, without incurring the cost of a complete
node_modules search.
In the rare cases when your package.json lists more than ten typed dependencies that haven't been im-
ported yet, this feature automatically disables itself to prevent slow project loading. To force the feature to
work, or to disable it entirely, you should be able to configure your editor. For Visual Studio Code, this is the
"Include Package JSON Auto Imports" (or typescript.preferences.includePackageJsonAutoImports )
setting.
For more details, you can see the proposal issue along with the implementing pull request.
680
TypeScript 4.0
We already wrote a bit about our new site, so you can read up more there; but it's worth mentioning that
we're still looking to hear what you think! If you have questions, comments, or suggestions, you can file
them over on the website's issue tracker.
Breaking Changes
lib.d.ts Changes
Our lib.d.ts declarations have changed - most specifically, types for the DOM have changed. The most
notable change may be the removal of document.origin which only worked in old versions of IE and
Safari MDN recommends moving to self.origin .
681
typescript
// @errors: 2611
class Base {
prop = 10;
}
// @errors: 2790
interface Thing {
prop: string;
}
For more details, read up on the relevant pull request for this change.
Go to TOC
682
TypeScript 4.1
// @errors: 2345
function setVerticalAlignment(location: "top" | "middle" | "bottom") {
// ...
}
setVerticalAlignment("middel");
This is pretty nice because string literal types can basically spell-check our string values.
We also like that string literals can be used as property names in mapped types. In this sense, they're also
usable as building blocks:
type Options = {
[K in "noImplicitAny" | "strictNullChecks" | "strictFunctionTypes"]?: boolean;
};
// same as
// type Options = {
// noImplicitAny?: boolean,
// strictNullChecks?: boolean,
// strictFunctionTypes?: boolean
// };
But there's another place that that string literal types could be used as building blocks: building other string
literal types.
That's why TypeScript 4.1 brings the template literal string type. It has the same syntax as template literal
strings in JavaScript, but is used in type positions. When you use it with concrete literal types, it produces a
new string literal type by concatenating the contents.
What happens when you have unions in substitution positions? It produces the set of every possible string
literal that could be represented by each union member.
This can be used beyond cute examples in release notes. For example, several libraries for UI components
have a way to specify both vertical and horizontal alignment in their APIs, often with both at once using a
single string like "bottom-right" . Between vertically aligning with "top" , "middle" , and "bottom" ,
and horizontally aligning with "left" , "center" , and "right" , there are 9 possible strings where each
of the former strings is connected with each of the latter strings using a dash.
683
typescript
// @errors: 2345
type VerticalAlignment = "top" | "middle" | "bottom";
type HorizontalAlignment = "left" | "center" | "right";
// Takes
// | "top-left" | "top-center" | "top-right"
// | "middle-left" | "middle-center" | "middle-right"
// | "bottom-left" | "bottom-center" | "bottom-right"
setAlignment("top-left"); // works!
setAlignment("top-middel"); // error!
setAlignment("top-pot"); // error! but good doughnuts if you're ever in Seattle
While there are lots of examples of this sort of API in the wild, this is still a bit of a toy example since we
could write these out manually. In fact, for 9 strings, this is likely fine; but when you need a ton of strings,
you should consider automatically generating them ahead of time to save work on every type-check (or just
use string , which will be much simpler to comprehend).
Some of the real value comes from dynamically creating new string literals. For example, imagine a make‐
WatchedObject API that takes an object and produces a mostly identical object, but with a new on
method to detect for changes to the properties.
person.on("firstNameChanged", () => {
console.log(`firstName was changed!`);
});
Notice that on listens on the event "firstNameChanged" , not just "firstName" . How would we type
this?
type PropEventSource<T> = {
on(eventName: `${string & keyof T}Changed`, callback: () => void): void;
};
With this, we can build something that errors when we give the wrong property!
// @errors: 2345
type PropEventSource<T> = {
on(eventName: `${string & keyof T}Changed`, callback: () => void): void;
};
declare function makeWatchedObject<T>(obj: T): T & PropEventSource<T>;
let person = makeWatchedObject({
firstName: "Homer",
age: 42, // give-or-take
location: "Springfield",
684
TypeScript 4.1
});
// ---cut---
// error!
person.on("firstName", () => {});
// error!
person.on("frstNameChanged", () => {});
We can also do something special in template literal types: we can infer from substitution positions. We can
make our last example generic to infer from parts of the eventName string to figure out the associated
property.
type PropEventSource<T> = {
on<K extends string & keyof T>
(eventName: `${K}Changed`, callback: (newValue: T[K]) => void ): void;
};
Here we made on into a generic method. When a user calls with the string "firstNameChanged' ,
TypeScript will try to infer the right type for K . To do that, it will match K against the content prior to
"Changed" and infer the string "firstName" . Once TypeScript figures that out, the on method can fetch
the type of firstName on the original object, which is string in this case. Similarly, when we call with
"ageChanged" , it finds the type for the property age which is number ).
Inference can be combined in different ways, often to deconstruct strings, and reconstruct them in different
ways. In fact, to help with modifying these string literal types, we've added a few new utility type aliases for
modifying casing in letters (i.e. converting to lowercase and uppercase characters).
685
typescript
The new type aliases are Uppercase , Lowercase , Capitalize and Uncapitalize . The first two trans-
form every character in a string, and the latter two transform only the first character in a string.
For more details, see the original pull request and the in-progress pull request to switch to type alias
helpers.
type Options = {
[K in "noImplicitAny" | "strictNullChecks" | "strictFunctionTypes"]?: boolean;
};
// same as
// type Options = {
// noImplicitAny?: boolean,
// strictNullChecks?: boolean,
// strictFunctionTypes?: boolean
// };
/// 'Partial<T>' is the same as 'T', but with each property marked optional.
type Partial<T> = {
[K in keyof T]?: T[K];
};
Until now, mapped types could only produce new object types with keys that you provided them; however,
lots of the time you want to be able to create new keys, or filter out keys, based on the inputs.
That's why TypeScript 4.1 allows you to re-map keys in mapped types with a new as clause.
type MappedTypeWithNewKeys<T> = {
[K in keyof T as NewKeyType]: T[K]
// ^^^^^^^^^^^^^
// This is the new syntax!
}
With this new as clause, you can leverage features like template literal types to easily create property
names based off of old ones.
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K]
};
interface Person {
name: string;
age: number;
location: string;
}
686
TypeScript 4.1
and you can even filter out keys by producing never . That means you don't have to use an extra Omit
helper type in some cases.
interface Circle {
kind: "circle";
radius: number;
}
For more information, take a look at the original pull request over on GitHub.
Expressing this in TypeScript's type system was, for all practical intents and purposes, not possible. While
there were hacks to achieve this, the types ended up looking very unreasonable.
That's why TypeScript 4.1 eases some restrictions on conditional types - so that they can model these pat-
terns. In TypeScript 4.1, conditional types can now immediately reference themselves within their branches,
making it easier to write recursive type aliases.
For example, if we wanted to write a type to get the element types of nested arrays, we could write the fol-
lowing deepFlatten type.
Similarly, in TypeScript 4.1 we can write an Awaited type to deeply unwrap Promise s.
687
typescript
p: Promise<T>,
onFulfilled: (value: Awaited<T>) => U
): Promise<Awaited<U>>;
Keep in mind that while these recursive types are powerful, but they should be used responsibly and
sparingly.
First off, these types can do a lot of work which means that they can increase type-checking time. Trying to
model numbers in the Collatz conjecture or Fibonacci sequence might be fun, but don't ship that in .d.ts
files on npm.
But apart from being computationally intensive, these types can hit an internal recursion depth limit on suf-
ficiently-complex inputs. When that recursion limit is hit, that results in a compile-time error. In general, it's
better not to use these types at all than to write something that fails on more realistic examples.
interface Options {
path: string;
permissions: number;
In the above example, Options has an index signature that says any accessed property that's not already
listed should have the type string | number . This is often convenient for optimistic code that assumes
you know what you're doing, but the truth is that most values in JavaScript do not support every potential
property name. Most types will not, for example, have a value for a property key created by
Math.random() like in the previous example. For many users, this behavior was undesirable, and felt like it
wasn't leveraging the full strict-checking of strictNullChecks .
688
TypeScript 4.1
That's why TypeScript 4.1 ships with a new flag called noUncheckedIndexedAccess . Under this new mode,
every property access (like foo.bar ) or indexed access (like foo["bar"] ) is considered potentially unde-
fined. That means that in our last example, opts.yadda will have the type string | number | unde‐
fined as opposed to just string | number . If you need to access that property, you'll either have to
check for its existence first or use a non-null assertion operator (the postfix ! character).
// @errors: 2532
// @noUncheckedIndexedAccess
interface Options {
path: string;
permissions: number;
One consequence of using noUncheckedIndexedAccess is that indexing into an array is also more strictly
checked, even in a bounds-checked loop.
// @errors: 2532
// @noUncheckedIndexedAccess
function screamLines(strs: string[]) {
// This will have issues
for (let i = 0; i < strs.length; i++) {
console.log(strs[i].toUpperCase());
}
}
If you don't need the indexes, you can iterate over individual elements by using a for - of loop or a forE‐
ach call.
// @noUncheckedIndexedAccess
function screamLines(strs: string[]) {
// This works fine
for (const str of strs) {
console.log(str.toUpperCase());
}
689
typescript
This flag can be handy for catching out-of-bounds errors, but it might be noisy for a lot of code, so it is not
automatically enabled by the strict flag; however, if this feature is interesting to you, you should feel free
to try it and determine whether it makes sense for your team's codebase!
Unfortunately, specifying paths to enable path-mapping required also specifying an option called base‐
Url , which allows bare specifier paths to be reached relative to the baseUrl too. This also often caused
poor paths to be used by auto-imports.
In TypeScript 4.1, the paths option can be used without baseUrl . This helps avoid some of these issues.
react-jsx
react-jsxdev
These options are intended for production and development compiles respectively. Often, the options from
one can extend from the other. For example, a tsconfig.json for production builds might look like the
following:
// ./src/tsconfig.json
{
"compilerOptions": {
"module": "esnext",
"target": "es2015",
"jsx": "react-jsx",
"strict": true
690
TypeScript 4.1
},
"include": ["./**/*"]
}
and one for development builds might look like the following:
// ./src/tsconfig.dev.json
{
"extends": "./tsconfig.json",
"compilerOptions": {
"jsx": "react-jsxdev"
}
}
// @filename: first.ts
export class C {}
// @filename: main.ts
import * as first from "./first";
/**
* @see first.C
*/
function related() {}
Breaking Changes
lib.d.ts Changes
lib.d.ts may have a set of changed APIs, potentially in part due to how the DOM types are automatically
generated. One specific change is that Reflect.enumerate has been removed, as it was removed from
ES2016.
691
typescript
For example, previously the type for x here was { someProp: string } .
However, in TypeScript 4.1, we are more careful about how we determine this type. Since nothing is known
about the type on the left side of the && , we propagate any and unknown outward instead of the type on
the right side.
The most common pattern we saw of this tended to be when checking compatibility with boolean s, espe-
cially in predicate functions.
Often the appropriate fix is to switch from foo && someExpression to !!foo && someExpression .
resolve()
~~~~~~~~~
error TS2554: Expected 1 arguments, but got 0.
An argument for 'value' was not provided.
This is because resolve no longer has an optional parameter, so by default, it must now be passed a
value. Often this catches legitimate bugs with using Promise s. The typical fix is to pass it the correct argu-
ment, and sometimes to add an explicit type argument.
692
TypeScript 4.1
However, sometimes resolve() really does need to be called without an argument. In these cases, we can
give Promise an explicit void generic type argument (i.e. write it out as Promise<void> ). This leverages
new functionality in TypeScript 4.1 where a potentially- void trailing parameter can become optional.
TypeScript 4.1 ships with a quick fix to help fix this break.
interface Person {
name: string;
age: number;
location: string;
}
interface Animal {
name: string;
owner: Person;
}
Here, if pet is defined, the properties of pet.owner will be spread in - otherwise, no properties will be
spread into the returned object.
The return type of copyOwner was previously a union type based on each spread:
693
typescript
This modeled exactly how the operation would occur: if pet was defined, all the properties from Person
would be present; otherwise, none of them would be defined on the result. It was an all-or-nothing
operation.
However, we've seen this pattern taken to the extreme, with hundreds of spreads in a single object, each
spread potentially adding in hundreds or thousands of properties. It turns out that for various reasons, this
ends up being extremely expensive, and usually for not much benefit.
{
x: number;
name?: string;
age?: number;
location?: string;
}
For more details, see the original change. While this behavior is not entirely consistent right now, we expect
a future release will produce cleaner and more predictable results.
Go to TOC
694
TypeScript 4.2
TypeScript has always used a set of rules and guesses for when to reuse type aliases when printing out
types. For example, take the following code snippet.
If we hover our mouse over x in an editor like Visual Studio, Visual Studio Code, or the TypeScript
Playground, we'll get a quick info panel that shows the type BasicPrimitive . Likewise, if we get the dec-
laration file output ( .d.ts output) for this file, TypeScript will say that doStuff returns BasicPrimitive .
return value;
}
We can see what happens in the TypeScript 4.1 playground. While we might want TypeScript to display the
return type of doStuff as BasicPrimitive | undefined , it instead displays string | number | bool‐
ean | undefined ! What gives?
Well this has to do with how TypeScript represents types internally. When creating a union type out of one
or more union types, it will always normalize those types into a new flattened union type - but doing that
loses information. The type-checker would have to find every combination of types from string | number
| boolean | undefined to see what type aliases could have been used, and even then, there might be
multiple type aliases to string | number | boolean .
In TypeScript 4.2, our internals are a little smarter. We keep track of how types were constructed by keep-
ing around parts of how they were originally written and constructed over time. We also keep track of, and
differentiate, type aliases to instances of other aliases!
695
typescript
Being able to print back the types based on how you used them in your code means that as a TypeScript
user, you can avoid some unfortunately humongous types getting displayed, and that often translates to
getting better .d.ts file output, error messages, and in-editor type displays in quick info and signature
help. This can help TypeScript feel a little bit more approachable for newcomers.
For more information, check out the first pull request that improves various cases around preserving union
type aliases, along with a second pull request that preserves indirect aliases.
Over time, TypeScript's tuple types have become more and more sophisticated, since they're also used to
model things like parameter lists in JavaScript. As a result, they can have optional elements and rest ele-
ments, and can even have labels for tooling and readability.
e = ["hello", "world"];
e = ["hello", "world", false];
e = ["hello", "world", true, false, true];
In TypeScript 4.2, rest elements specifically been expanded in how they can be used. In prior versions,
TypeScript only allowed ...rest elements at the very last position of a tuple type.
However, now rest elements can occur anywhere within a tuple - with only a few restrictions.
foo = [123];
foo = ["hello", 123];
foo = ["hello!", "hello!", "hello!", 123];
696
TypeScript 4.2
The only restriction is that a rest element can be placed anywhere in a tuple, so long as it's not followed by
another optional element or rest element. In other words, only one rest element per tuple, and no optional
elements after rest elements.
These non-trailing rest elements can be used to model functions that take any number of leading
arguments, followed by a few fixed ones.
doStuff(/*shouldCapitalize:*/ false)
doStuff("fee", "fi", "fo", "fum", /*shouldCapitalize:*/ true);
Even though JavaScript doesn't have any syntax to model leading rest parameters, we were still able to de-
clare doStuff as a function that takes leading arguments by declaring the ...args rest parameter with a
tuple type that uses a leading rest element. This can help model lots of existing JavaScript out there!
// @errors: 2361
"foo" in 42;
This check is fairly conservative for the most part, so if you have received an error about this, it is likely an
issue in the code.
A big thanks to our external contributor Jonas Hübotter for their pull request!
--noPropertyAccessFromIndexSignature
Back when TypeScript first introduced index signatures, you could only get properties declared by them with
"bracketed" element access syntax like person["name"] .
interface SomeType {
/** This is an index signature. */
[propName: string]: any;
697
typescript
This ended up being cumbersome in situations where we need to work with objects that have arbitrary
properties. For example, imagine an API where it's common to misspell a property name by adding an extra
s character at the end.
interface Options {
/** File patterns to be excluded. */
exclude?: string[];
/**
* It handles any extra properties that we haven't declared as type 'any'.
*/
[x: string]: any;
}
To make these types of situations easier, a while back, TypeScript made it possible to use "dotted" property
access syntax like person.name when a type had a string index signature. This also made it easier to tran-
sition existing JavaScript code over to TypeScript.
However, loosening the restriction also meant that misspelling an explicitly declared property became much
easier.
interface Options {
/** File patterns to be excluded. */
exclude?: string[];
/**
* It handles any extra properties that we haven't declared as type 'any'.
*/
[x: string]: any;
}
// ---cut---
function processOptions(opts: Options) {
// ...
698
TypeScript 4.2
In some cases, users would prefer to explicitly opt into the index signature - they would prefer to get an er-
ror message when a dotted property access doesn't correspond to a specific property declaration.
That's why TypeScript introduces a new flag called noPropertyAccessFromIndexSignature . Under this
mode, you'll be opted in to TypeScript's older behavior that issues an error. This new setting is not under
the strict family of flags, since we believe users will find it more useful on certain codebases than others.
You can understand this feature in more detail by reading up on the corresponding pull request. We'd also
like to extend a big thanks to Wenlu Wang who sent us this pull request!
// @errors: 2511
abstract class Shape {
abstract getArea(): number;
}
new Shape();
constructor(sideLength: number) {
super();
this.#sideLength = sideLength;
}
getArea() {
return this.#sideLength ** 2;
}
}
// Works fine.
new Square(42);
To make sure this restriction in new -ing up abstract classes is consistently applied, you can't assign an
abstract class to anything that expects a construct signature.
// @errors: 2322
abstract class Shape {
abstract getArea(): number;
}
// ---cut---
interface HasArea {
getArea(): number;
}
This does the right thing in case we intend to run code like new Ctor , but it's overly-restrictive in case we
want to write a subclass of Ctor .
699
typescript
// @errors: 2345
abstract class Shape {
abstract getArea(): number;
}
interface HasArea {
getArea(): number;
}
It also doesn't work well with built-in helper types like InstanceType .
// @errors: 2344
abstract class Shape {
abstract getArea(): number;
}
// ---cut---
type MyInstance = InstanceType<typeof Shape>;
That's why TypeScript 4.2 allows you to specify an abstract modifier on constructor signatures.
// Works!
let Ctor: abstract new () => HasArea = Shape;
Adding the abstract modifier to a construct signature signals that you can pass in abstract
constructors. It doesn't stop you from passing in other classes/constructor functions that are "concrete" - it
really just signals that there's no intent to run the constructor directly, so it's safe to pass in either class
type.
This feature allows us to write mixin factories in a way that supports abstract classes. For example, in the
following code snippet, we're able to use the mixin function withStyles with the abstract class
SuperClass .
700
TypeScript 4.2
Note that withStyles is demonstrating a specific rule, where a class (like StyledClass ) that extends a
value that's generic and bounded by an abstract constructor (like Ctor ) has to also be declared ab‐
stract . This is because there's no way to know if a class with more abstract members was passed in, and
so it's impossible to know whether the subclass implements all the abstract members.
You can read up more on abstract construct signatures on its pull request.
tsc --explainFiles
When using this option, the TypeScript compiler will give some very verbose output about why a file ended
up in your program. To read it more easily, you can forward the output to a file, or pipe it to a program that
can easily view it.
Typically, the output will start out by listing out reasons for including lib.d.ts files, then for local files,
and then node_modules files.
TS_Compiler_Directory/4.2.2/lib/lib.es5.d.ts
Library referenced via 'es5' from file
'TS_Compiler_Directory/4.2.2/lib/lib.es2015.d.ts'
TS_Compiler_Directory/4.2.2/lib/lib.es2015.d.ts
701
typescript
foo.ts
Matched by include pattern '**/*' in 'tsconfig.json'
Right now, we make no guarantees about the output format - it might change over time. On that note,
we're interested in improving this format if you have any suggestions!
702
TypeScript 4.2
Previously, if _first was never used later on, TypeScript would issue an error under noUnusedLocals .
Now, TypeScript will recognize that _first was intentionally named with an underscore because there was
no intent to use it.
Of course, for any movie title not yet in the dictionary, movieWatchCount[title] will be undefined
(TypeScript 4.1 added the option noUncheckedIndexedAccess to include undefined when reading from
an index signature like this). Even though it's clear that there must be some strings not present in movie‐
WatchCount , previous versions of TypeScript treated optional object properties as unassignable to other-
wise compatible index signatures, due to the presence of undefined .
type WesAndersonWatchCount = {
"Fantastic Mr. Fox"?: number;
"The Royal Tenenbaums"?: number;
"Moonrise Kingdom"?: number;
"The Grand Budapest Hotel"?: number;
};
TypeScript 4.2 allows this assignment. However, it does not allow the assignment of non-optional properties
with undefined in their types, nor does it allow writing undefined to a specific key:
703
typescript
// @errors: 2322
type BatmanWatchCount = {
"Batman Begins": number | undefined;
"The Dark Knight": number | undefined;
"The Dark Knight Rises": number | undefined;
};
The new rule also does not apply to number index signatures, since they are assumed to be array-like and
dense:
// @errors: 2322
declare let sortOfArrayish: { [key: number]: string };
declare let numberKeys: { 42?: string };
sortOfArrayish = numberKeys;
You can get a better sense of this change by reading up on the original PR.
704
TypeScript 4.2
An un-declared function `foo` being called, with a quick fix scaffolding out the new contents of the file
Breaking Changes
We always strive to minimize breaking changes in a release. TypeScript 4.2 contains some breaking
changes, but we believe they should be manageable in an upgrade.
lib.d.ts Updates
As with every TypeScript version, declarations for lib.d.ts (especially the declarations generated for web
contexts), have changed. There are various changes, though Intl and ResizeObserver 's may end up be-
ing the most disruptive.
// @errors: 7057
function* g1() {
const value = yield 1;
}
function* g2() {
// No error.
705
typescript
function* g3() {
// No error.
// `yield 1` is contextually typed by 'string'.
const value: string = yield 1;
}
f<T>(100);
This may impact you if you were leveraging TypeScript's API to parse type constructs in JavaScript files,
which may have occurred when trying to parse Flow files.
706
TypeScript 4.2
Sometimes these tuple types can accidentally grow to be huge, and that can make type-checking take a
long time. Instead of letting the type-checking process hang (which is especially bad in editor scenarios),
TypeScript has a limiter in place to avoid doing all that work.
Instead, your import paths should reflect whatever your loader will do at runtime. Any of the following im-
ports might be usable instead.
The beta version of TypeScript 4.2 included a change in inference to template strings. In this change, tem-
plate string literals would either be given template string types or simplify to multiple string literal types.
These types would then widen to string when assigning to mutable variables.
// 'bar' is constant.
// It has type '`hello ${string}`'.
const bar = `hello ${yourName}`;
// 'baz' is mutable.
// It has type 'string'.
let baz = `hello ${yourName}`;
For that reason, we believed that making template string expressions have template string types would be
"consistent"; however, from what we've seen and heard, that isn't always desirable.
707
typescript
In response, we've reverted this feature (and potential breaking change). If you do want a template string
expression to be given a literal-like type, you can always add as const to the end of it.
Go to TOC
708
TypeScript 4.3
class Thing {
#size = 0;
get size() {
return this.#size;
}
set size(value) {
let num = Number(value);
this.#size = num;
}
}
How would we type this JavaScript code in TypeScript? Well, technically we don't have to do anything spe-
cial here - TypeScript can look at this with no explicit types and can figure out that size is a number.
The problem is that size allows you to assign more than just number s to it. We could get around this by
saying that size has the type unknown or any like in this snippet:
class Thing {
// ...
get size(): unknown {
return this.#size;
}
}
But that's no good - unknown forces people reading size to do a type assertion, and any won't catch any
mistakes. If we really want to model APIs that convert values, previous versions of TypeScript forced us to
pick between being precise (which makes reading values easier, and writing harder) and being permissive
(which makes writing values easier, and reading harder).
That's why TypeScript 4.3 allows you to specify types for reading and writing to properties.
class Thing {
#size = 0;
709
typescript
this.#size = num;
}
}
In the above example, our set accessor takes a broader set of types ( string s, boolean s, and num‐
ber s), but our get accessor always guarantees it will be a number . Now we can finally assign other types
to these properties with no errors!
class Thing {
#size = 0;
this.#size = num;
}
}
// ---cut---
let thing = new Thing();
When considering how two properties with the same name relate to each other, TypeScript will only use the
"reading" type (e.g. the type on the get accessor above). "Writing" types are only considered when direct-
ly writing to a property.
Keep in mind, this isn't a pattern that's limited to classes. You can write getters and setters with different
types in object literals.
710
TypeScript 4.3
size = num;
},
};
}
In fact, we've added syntax to interfaces/object types to support different reading/writing types on
properties.
// Now valid!
interface Thing {
get size(): number
set size(value: number | string | boolean);
}
One limitation of using different types for reading and writing properties is that the type for reading a prop-
erty has to be assignable to the type that you're writing. In other words, the getter type has to be as-
signable to the setter. This ensures some level of consistency, so that a property is always assignable to
itself.
For more information on this feature, take a look at the implementing pull request.
One big one is missing renames. For example, take the following classes:
class SomeComponent {
show() {
// ...
}
hide() {
// ...
}
}
711
typescript
SpecializedComponent subclasses SomeComponent , and overrides the show and hide methods. What
happens if someone decides to rip out show and hide and replace them with a single method?
class SomeComponent {
- show() {
- // ...
- }
- hide() {
- // ...
- }
+ setVisible(value: boolean) {
+ // ...
+ }
}
class SpecializedComponent extends SomeComponent {
show() {
// ...
}
hide() {
// ...
}
}
Oh no! Our SpecializedComponent didn't get updated. Now it's just adding these two useless show and
hide methods that probably won't get called.
Part of the issue here is that a user can't make it clear whether they meant to add a new method, or to
override an existing one. That's why TypeScript 4.3 adds the override keyword.
When a method is marked with override , TypeScript will always make sure that a method with the same
name exists in a the base class.
// @noImplicitOverride
// @errors: 4113
class SomeComponent {
setVisible(value: boolean) {
// ...
}
}
class SpecializedComponent extends SomeComponent {
override show() {
}
}
This is a big improvement, but it doesn't help if you forget to write override on a method - and that's a
big mistake users can run into also.
712
TypeScript 4.3
For example, you might accidentally "trample over" a method that exists in a base class without realizing it.
class Base {
someHelperMethod() {
// ...
}
}
That's why TypeScript 4.3 also provides a new noImplicitOverride flag. When this option is turned on, it
becomes an error to override any method from a superclass unless you explicitly use an override
keyword. In that last example, TypeScript would error under noImplicitOverride , and give us a clue that
we probably need to rename our method inside of Derived .
We'd like to extend our thanks to our community for the implementation here. The work for these items was
implemented in a pull request by Wenlu Wang, though an earlier pull request implementing only the over‐
ride keyword by Paul Cody Johnston served as a basis for direction and discussion. We extend our grati-
tude for putting in the time for these features.
// Works!
s1 = s2;
The first change we made is just in when TypeScript will infer a template string type. When a template
string is contextually typed by a string-literal-like type (i.e. when TypeScript sees we're passing a template
string to something that takes a literal type) it will try to give that expression a template type.
713
typescript
This also kicks in when inferring types, and the type parameter extends string
// Previously: string
// Now : `hello ${string}`
let x2 = f(`hello ${s}`);
The second major change here is that TypeScript can now better-relate, and infer between, different tem-
plate string types.
s1 = s2;
s1 = s3;
When checking against a string literal type like on s2 , TypeScript could match against the string contents
and figure out that s2 was compatible with s1 in the first assignment; however, as soon as it saw another
template string, it just gave up. As a result, assignments like s3 to s1 just didn't work.
TypeScript now actually does the work to prove whether or not each part of a template string can success-
fully match. You can now mix and match template strings with different substitutions and TypeScript will do
a good job to figure out whether they're really compatible.
In doing this work, we were also sure to add better inference capabilities. You can see an example of these
in action:
714
TypeScript 4.3
For more information, see the original pull request on leveraging contextual types, along with the pull re-
quest that improved inference and checking between template types.
class Foo {
#someMethod() {
//...
}
get #someValue() {
return 100;
}
publicMethod() {
// These work.
// We can access private-named members inside this class.
this.#someMethod();
return this.#someValue;
}
}
new Foo().#someMethod();
// ~~~~~~~~~~~
// error!
// Property '#someMethod' is not accessible
// outside class 'Foo' because it has a private identifier.
new Foo().#someValue;
// ~~~~~~~~~~
// error!
// Property '#someValue' is not accessible
// outside class 'Foo' because it has a private identifier.
Even more broadly, static members can now also have private names.
class Foo {
static #someMethod() {
// ...
}
}
Foo.#someMethod();
// ~~~~~~~~~~~
// error!
// Property '#someMethod' is not accessible
// outside class 'Foo' because it has a private identifier.
715
typescript
This feature was authored in a pull request from our friends at Bloomberg - written by Titian Cernicova-
Dragomirand Kubilay Kahveci, with support and expertise from Joey Watts, Rob Palmer, and Tim McClure.
We'd like to extend our thanks to all of them!
abstract class C {
constructor(a: string, b: number) {
// ...
}
}
This is thanks to work done in TypeScript 4.2, where construct signatures can be marked as abstract:
type MyConstructorOf<T> = {
abstract new(...args: any[]): T;
}
For some motivation, let's say we're trying to write a function called makeUnique . It'll take a Set or an
Array of elements, and if it's given an Array , it'll sort that Array remove duplicates according to some
comparison function. After all that, it will return the original collection.
function makeUnique<T>(
collection: Set<T> | T[],
comparer: (x: T, y: T) => number
): Set<T> | T[] {
// Early bail-out if we have a Set.
// We assume the elements are already unique.
if (collection instanceof Set) {
return collection;
}
716
TypeScript 4.3
Let's leave questions about this function's implementation aside, and assume it arose from the require-
ments of a broader application. Something that you might notice is that the signature doesn't capture the
original type of collection . We can do that by adding a type parameter called C in place of where we've
written Set<T> | T[] .
In TypeScript 4.2 and earlier, you'd end up with a bunch of errors as soon as you tried this.
717
typescript
The issue is that when we perform our collection instanceof Set check, we're expecting that to act as
a type guard that narrows the type from Set<T> | T[] to Set<T> and T[] depending on the branch
we're in; however, we're not dealing with a Set<T> | T[] , we're trying to narrow the generic value col‐
lection , whose type is C .
It's a very subtle distinction, but it makes a difference. TypeScript can't just grab the constraint of C (which
is Set<T> | T[] ) and narrow that. If TypeScript did try to narrow from Set<T> | T[] , it would forget
that collection is also a C in each branch because there's no easy way to preserve that information. If
hypothetically TypeScript tried that approach, it would break the above example in a different way. At the
return positions, where the function expects values with the type C , we would instead get a Set<T> and a
T[] in each branch, which TypeScript would reject.
function makeUnique<T>(
collection: Set<T> | T[],
comparer: (x: T, y: T) => number
): Set<T> | T[] {
// Early bail-out if we have a Set.
// We assume the elements are already unique.
if (collection instanceof Set) {
return collection;
// ~~~~~~~~~~
// error: Type 'Set<T>' is not assignable to type 'C'.
// 'Set<T>' is assignable to the constraint of type 'C', but
// 'C' could be instantiated with a different subtype of constraint
'Set<T> | T[]'.
}
// ...
return collection;
// ~~~~~~~~~~
// error: Type 'T[]' is not assignable to type 'C'.
// 'T[]' is assignable to the constraint of type 'C', but
// 'C' could be instantiated with a different subtype of constraint
'Set<T> | T[]'.
}
So how does TypeScript 4.3 change things? Well, basically in a few key places when writing code, all the
type system really cares about is the constraint of a type. For example, when we write collec‐
tion.length , TypeScript doesn't care about the fact that collection has the type C , it only cares about
the properties available, which are determined by the constraint T[] | Set<T> .
In cases like this, TypeScript will grab the narrowed type of the constraint because that will give you the
data you care about; however, in any other case, we'll just try to narrow the original generic type (and often
end up with the original generic type).
In other words, based on how you use a generic value, TypeScript will narrow it a little differently. The end
result is that the entire above example compiles with no type-checking errors.
For more details, you can look at the original pull request on GitHub.
718
TypeScript 4.3
This change was contributed by Jack Works, and we extend our thanks to them!
class Foo {
hello = "hello";
world = 1234;
// Valid assigment
instance["whatever"] = 42;
Up until now, an index signature could only be declared on the instance side of a class. Thanks to a pull re-
quest from Wenlu Wang, index signatures can now be declared as static .
class Foo {
static hello = "hello";
static world = 1234;
// Valid.
Foo["whatever"] = 42;
719
typescript
The same sorts of rules apply for index signatures on the static side of a class as they do for the instance
side - namely, that every other static property has to be compatible with the index signature.
class Foo {
static prop = true;
// ~~~~
// Error! Property 'prop' of type 'boolean'
// is not assignable to string index type
// 'string | number | undefined'.
1MB to 411 KB
14.9MB to 1MB
1345MB to 467MB
Needless to say, these sorts of savings in size translate to slightly faster build times as well.
That's why on top of .tsbuildinfo size improvements, TypeScript 4.3 also ships some changes to incre‐
mental and --watch modes that make the first build of a project with these flags just as fast as an ordi-
nary build! To do this, much of the information that would ordinarily be computed up-front is instead done
on an on-demand basis for later builds. While this can add some overhead to a subsequent build,
TypeScript's incremental and --watch functionality will still typically operate on a much smaller set of
files, and any needed information will be saved afterwards. In a sense, incremental and --watch builds
will "warm up" and get faster at compiling files once you've updated them a few times.
In a repository with 3000 files, this reduced initial build times to almost a third!
This work was started by Tobias Koppers, whose work ensued in the resulting final change for this function-
ality. We'd like to extend a great thanks to Tobias for helping us find these opportunities for improvements!
720
TypeScript 4.3
instead of
This causes some pain when writing out a full import statement from scratch because auto-complete wasn't
able to work correctly. For example, if you start writing something like import { , TypeScript has no idea
what module you're planning on importing from, so it couldn't provide any scoped-down completions.
To alleviate this, we've leveraged the power of auto-imports! Auto-imports already deal with the issue of not
being able to narrow down completions from a specific module - their whole point is to provide every possi-
ble export and automatically insert an import statement at the top of your file.
So when you now start writing an import statement that doesn't have a path, we'll provide you with a list
of possible imports. When you commit a completion, we'll complete the full import statement, including the
path that you were going to write.
721
typescript
This work requires editors that specifically support the feature. You'll be able to try this out by using the lat-
est Insiders versions of Visual Studio Code.
For example, you'll be able to go-to-definition on bar in @link bar in the example below and a
TypeScript-supported editor will jump to bar 's function declaration.
722
TypeScript 4.3
/**
* To be called 70 to 80 days after {@link plantCarrot}.
*/
function harvestCarrot(carrot: Carrot) {}
/**
* Call early in spring for best results. Added in v2.1.0.
* @param seed Make sure it's a carrot seed!
*/
function plantCarrot(seed: Seed) {
// TODO: some gardening
}
723
typescript
Up until now, TypeScript's editor functionality wouldn't even attempt to read this file, so go-to-definition
would typically fail. At best, go-to-definition would jump to a declaration like declare module "*.css" if it
could find something along those lines.
TypeScript's language service now tries to jump to the correct file when you perform a go-to-definition on
relative file paths, even if they're not JavaScript or TypeScript files! Try it out with imports to CSS, SVGs,
PNGs, font files, Vue files, and more.
For more information, you can check out the implementing pull request.
Breaking Changes
lib.d.ts Changes
As with every TypeScript version, declarations for lib.d.ts (especially the declarations generated for web
contexts), have changed. In this release, we leveraged Mozilla's browser-compat-data to remove APIs that
no browser implements. While it is unlike that you are using them, APIs such as Account ,
AssertionOptions , RTCStatsEventInit , MSGestureEvent , DeviceLightEvent , MSPointerEvent ,
ServiceWorkerMessageEvent , and WebAuthentication have all been removed from lib.d.ts . This is
discussed in some detail here.
https://github.com/microsoft/TypeScript-DOM-lib-generator/issues/991
Now that the feature is in JavaScript we are changing the default to true for ES2022 and above, including
ESNext.
if (p) {
// ~
// Error!
// This condition will always return true since
724
TypeScript 4.3
In TypeScript 4.3, if a value with a union enum type is compared with a numeric literal that it could never
be equal to, then the type-checker will issue an error.
enum E {
A = 0,
B = 1,
}
function doSomething(x: E) {
// Error! This condition will always return 'false' since the types 'E' and '-1'
have no overlap.
if (x === -1) {
// ...
}
}
As a workaround, you can re-write an annotation to include the appropriate literal type.
enum E {
A = 0,
B = 1,
}
// Include -1 in the type, if we're really certain that -1 can come through.
function doSomething(x: E | -1) {
if (x === -1) {
// ...
}
}
enum E {
A = 0,
B = 1,
}
function doSomething(x: E) {
// Use a type asertion on 'x' because we know we're not actually just dealing
with values from 'E'.
if ((x as number) === -1) {
// ...
}
}
725
typescript
Alternatively, you can re-declare your enum to have a non-trivial initializer so that any number is both as-
signable and comparable to that enum. This may be useful if the intent is for the enum to specify a few
well-known values.
enum E {
// the leading + on 0 opts TypeScript out of inferring a union enum.
A = +0,
B = 1,
}
Go to TOC
726
TypeScript 4.4
In this example, we checked whether arg was a string . TypeScript recognized the typeof arg ===
"string" check, which it considered a type guard, and knew that arg was a string inside the body of
the if block. That let us access string methods like toUpperCase() without getting an error.
However, what would happen if we moved the condition out to a constant called argIsString ?
In previous versions of TypeScript, this would be an error - even though argIsString was assigned the
value of a type guard, TypeScript simply lost that information. That's unfortunate since we might want to
re-use the same check in several places. To get around that, users often have to repeat themselves or use
type assertions (a.k.a. casts).
In TypeScript 4.4, that is no longer the case. The above example works with no errors! When TypeScript
sees that we are testing a constant value, it will do a little bit of extra work to see if it contains a type
guard. If that type guard operates on a const , a readonly property, or an un-modified parameter, then
TypeScript is able to narrow that value appropriately.
Different sorts of type guard conditions are preserved - not just typeof checks. For example, checks on
discriminated unions work like a charm.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; sideLength: number };
727
typescript
Analysis on discriminants in 4.4 also goes a little bit deeper - we can now extract out discriminants and
TypeScript can narrow the original object.
type Shape =
| { kind: "circle"; radius: number }
| { kind: "square"; sideLength: number };
As another example, here's a function that checks whether two of its inputs have contents.
function doSomeChecks(
inputA: string | undefined,
inputB: string | undefined,
shouldDoExtraWork: boolean
) {
const mustDoWork = inputA && inputB && shouldDoExtraWork;
if (mustDoWork) {
// We can access 'string' properties on both 'inputA' and 'inputB'!
const upperA = inputA.toUpperCase();
const upperB = inputB.toUpperCase();
// ...
}
}
TypeScript can understand that both inputA and inputB are both present if mustDoWork is true . That
means we don't have to write a non-null assertion like inputA! to convince TypeScript that inputA isn't
undefined .
One neat feature here is that this analysis works transitively. TypeScript will hop through constants to un-
derstand what sorts of checks you've already performed.
728
TypeScript 4.4
Note that there's a cutoff - TypeScript doesn't go arbitrarily deep when checking these conditions, but its
analysis is deep enough for most checks.
This feature should make a lot of intuitive JavaScript code "just work" in TypeScript without it getting in
your way. For more details, check out the implementation on GitHub!
For example, we can write a type with an index signature that takes string keys and maps to boolean
values. If we try to assign anything other than a boolean value, we'll get an error.
While a Map might be a better data structure here (specifically, a Map<string, boolean> ), JavaScript ob-
jects are often more convenient to use or just happen to be what we're given to work with.
Similarly, Array<T> already defines a number index signature that lets us insert/retrieve values of type
T.
// ...
}
729
typescript
// Valid
arr[0] = "hello!";
Index signatures are very useful to express lots of code out in the wild; however, until now they've been
limited to string and number keys (and string index signatures have an intentional quirk where they
can accept number keys since they'll be coerced to strings anyway). That means that TypeScript didn't al-
low indexing objects with symbol keys. TypeScript also couldn't model an index signature of some subset
of string keys - for example, an index signature which describes just properties whose names start with
the text data- .
TypeScript 4.4 addresses these limitations, and allows index signatures for symbol s and template string
patterns.
For example, TypeScript now allows us to declare a type that can be keyed on arbitrary symbol s.
Similarly, we can write an index signature with template string pattern type. One use of this might be to ex-
empt properties starting with data- from TypeScript's excess property checking. When we pass an object
literal to something with an expected type, TypeScript will look for excess properties that weren't declared
in the expected type.
let a: Options = {
width: 100,
height: 100,
"data-blah": true,
};
730
TypeScript 4.4
let b: OptionsWithDataProps = {
width: 100,
height: 100,
"data-blah": true,
A final note on index signatures is that they now permit union types, as long as they're a union of infinite-
domain primitive types - specifically:
string
number
symbol
template string patterns (e.g. `hello-${string}` )
An index signature whose argument is a union of these types will de-sugar into several different index
signatures.
interface Data {
[optName: string | symbol]: any;
}
// Equivalent to
interface Data {
[optName: string]: any;
[optName: symbol]: any;
}
try {
// Who knows what this might throw...
executeSomeThirdPartyCode();
} catch (err) {
// err: any
console.error(err.message); // Allowed, because 'any'
err.thisWillProbablyFail(); // Allowed, because 'any' :(
}
731
typescript
Once TypeScript added the unknown type, it became clear that unknown was a better choice than any in
catch clause variables for users who want the highest degree of correctness and type-safety, since it nar-
rows better and forces us to test against arbitrary values. Eventually TypeScript 4.0 allowed users to specify
an explicit type annotation of unknown (or any ) on each catch clause variable so that we could opt into
stricter types on a case-by-case basis; however, for some, manually specifying : unknown on every catch
clause was a chore.
That's why TypeScript 4.4 introduces a new flag called useUnknownInCatchVariables . This flag changes
the default type of catch clause variables from any to unknown .
// @errors: 2571
declare function executeSomeThirdPartyCode(): void;
// ---cut---
try {
executeSomeThirdPartyCode();
} catch (err) {
// err: unknown
This flag is enabled under the strict family of options. That means that if you check your code using
strict , this option will automatically be turned on. You may end up with errors in TypeScript 4.4 such as
In cases where we don't want to deal with an unknown variable in a catch clause, we can always add an
explicit : any annotation so that we can opt out of stricter types.
732
TypeScript 4.4
interface Person {
name: string;
age?: number;
}
interface Person {
name: string;
age?: number | undefined;
}
What this meant is that a user could explicitly write undefined in place of age .
const p: Person = {
name: "Daniel",
age: undefined, // This is okay by default.
};
So by default, TypeScript doesn't distinguish between a present property with the value undefined and a
missing property. While this works most of the time, not all code in JavaScript makes the same
assumptions. Functions and operators like Object.assign , Object.keys , object spread ( { ...obj } ),
and for - in loops behave differently depending on whether or not a property actually exists on an object.
In the case of our Person example, this could potentially lead to runtime errors if the age property was
observed in a context where its presence was important.
In TypeScript 4.4, the new flag exactOptionalPropertyTypes specifies that optional property types
should be interpreted exactly as written, meaning that | undefined is not added to the type:
// @exactOptionalPropertyTypes
// @errors: 2322 2375
interface Person {
name: string;
age?: number;
}
// ---cut---
// With 'exactOptionalPropertyTypes' on:
const p: Person = {
name: "Daniel",
age: undefined, // Error! undefined isn't a number
};
733
typescript
This flag is not part of the strict family and needs to be turned on explicitly if you'd like this behavior. It
also requires strictNullChecks to be enabled as well. We've been making updates to DefinitelyTyped and
other definitions to try to make the transition as straightforward as possible, but you may encounter some
friction with this depending on how your code is structured.
For more information, you can take a look at the implementing pull request here.
These static blocks allow you to write a sequence of statements with their own scope that can access pri-
vate fields within the containing class. That means that we can write initialization code with all the capabili-
ties of writing statements, no leakage of variables, and full access to our class's internals.
get count() {
return Foo.#count;
}
static {
try {
const lastInstances = loadLastInstances();
Foo.#count += lastInstances.length;
}
catch {}
}
}
Without static blocks, writing the code above was possible, but often involved several different types of
hacks that had to compromise in some way.
Note that a class can have multiple static blocks, and they're run in the same order in which they're
written.
734
TypeScript 4.4
// Prints:
// 1
// 2
// 3
class Foo {
static prop = 1
static {
console.log(Foo.prop++);
}
static {
console.log(Foo.prop++);
}
static {
console.log(Foo.prop++);
}
}
We'd like to extend our thanks to Wenlu Wang for TypeScript's implementation of this feature. For more de-
tails, you can see that pull request here.
The new TypeScript `--help` menu where the output is bucketed into several different areas
735
typescript
Performance Improvements
Faster Declaration Emit
TypeScript now caches whether internal symbols are accessible in different contexts, along with how specific
types should be printed. These changes can improve TypeScript's general performance in code with fairly
complex types, and is especially observed when emitting .d.ts files under the declaration flag.
For more details, you can view the PR for path segment normalization along with the PR for slash
normalization.
We'd like to extend our thanks to David Michon who provided a simple and clean change to enable this per-
formance win.
736
TypeScript 4.4
That's why TypeScript now issues spelling suggestions in plain JavaScript files - ones without // @ts-
check or in a project with checkJs turned off. These are the same "Did you mean...?" suggestions that
TypeScript files already have, and now they're available in all JavaScript files in some form.
These spelling suggestions can provide a subtle clue that your code is wrong. We managed to find a few
bugs in existing code while testing this feature!
For more details on this new feature, take a look at the pull request!
Inlay Hints
TypeScript 4.4 provides support for inlay hints which can help display useful information like parameter
names and return types in your code. You can think of it as a sort of friendly "ghost text".
This feature was built by Wenlu Wang whose pull request has more details.
737
typescript
Wenlu also contributed the integration for inlay hints in Visual Studio Code which has shipped as part of the
July 2021 (1.59) release. If you'd like to try inlay hints out, make sure you're using a recent stable or insid-
ers version of the editor. You can also modify when and where inlay hints get displayed in Visual Studio
Code's settings.
A completion list containing unwieldy paths containing 'node_modules'. For example, the label for 'calendar-
Format' is 'node_modules/moment/moment' instead of 'moment'.
These paths end up being unwieldy and often misleading, especially given that the path that actually gets
inserted into your file needs to consider Node's node_modules resolution, path mappings, symlinks, and
re-exports.
That's why with TypeScript 4.4, the completion item label now shows the actual module path that will be
used for the import!
738
TypeScript 4.4
A completion list containing clean paths with no intermediate 'node_modules'. For example, the label for
'calendarFormat' is 'moment' instead of 'node_modules/moment/moment'.
Since this calculation can be expensive, completion lists containing many auto-imports may fill in the final
module specifiers in batches as you type more characters. It's possible that you'll still sometimes see the
old workspace-relative path labels; however, as your editing experience "warms up", they should get re-
placed with the actual path after another keystroke or two.
Breaking Changes
lib.d.ts Changes for TypeScript 4.4
As with every TypeScript version, declarations for lib.d.ts (especially the declarations generated for web
contexts), have changed. You can consult our list of known lib.dom.d.ts changes to understand what is
impacted.
// Imagine this is our imported module, and it has an export named 'foo'.
let fooModule = {
foo() {
console.log(this);
},
};
fooModule.foo();
This is not the way exported functions in ECMAScript are supposed to work when we call them. That's why
TypeScript 4.4 intentionally discards the this value when calling imported functions, by using the following
emit.
// Imagine this is our imported module, and it has an export named 'foo'.
let fooModule = {
foo() {
739
typescript
console.log(this);
},
};
To get around this, you can specifically add runtime checks to ensure that the thrown type matches your
expected type. Otherwise, you can just use a type assertion, add an explicit : any to your catch variable,
or turn off useUnknownInCatchVariables .
740
TypeScript 4.4
}
return "false";
}
TypeScript 4.4 now flags both. For more information, read up on the original change.
abstract class C {
abstract prop = 1;
// ~~~~
// Property 'prop' cannot have an initializer because it is marked abstract.
}
abstract class C {
abstract prop: number;
}
Go to TOC
741
typescript
There are two occasional downsides to including these declaration files with TypeScript though:
When you upgrade TypeScript, you're also forced to handle changes to TypeScript's built-in declaration
files, and this can be a challenge when the DOM APIs change as frequently as they do.
It is hard to customize these files to match your needs with the needs of your project's dependencies
(e.g. if your dependencies declare that they use the DOM APIs, you might also be forced into using the
DOM APIs).
TypeScript 4.5 introduces a way to override a specific built-in lib in a manner similar to how @types/
support works. When deciding which lib files TypeScript should include, it will first look for a scoped
@typescript/lib-* package in node_modules . For example, when including dom as an option in lib ,
TypeScript will use the types in node_modules/@typescript/lib-dom if available.
You can then use your package manager to install a specific package to take over for a given lib For ex-
ample, today TypeScript publishes versions of the DOM APIs on @types/web . If you wanted to lock your
project to a specific version of the DOM APIs, you could add this to your package.json :
{
"dependencies": {
"@typescript/lib-dom": "npm:@types/web"
}
}
Then from 4.5 onwards, you can update TypeScript and your dependency manager's lockfile will ensure that
it uses the exact same version of the DOM types. That means you get to update your types on your own
terms.
We'd like to give a shout-out to saschanaz who has been extremely helpful and patient as we've been build-
ing out and experimenting with this feature.
For more information, you can see the implementation of this change.
742
TypeScript 4.5
// A = string
type A = Awaited<Promise<string>>;
// B = number
type B = Awaited<Promise<Promise<number>>>;
// C = boolean | number
type C = Awaited<boolean | Promise<number>>;
The Awaited type can be helpful for modeling existing APIs, including JavaScript built-ins like
Promise.all , Promise.race , etc. In fact, some of the problems around inference with Promise.all
served as motivations for Awaited . Here's an example that fails in TypeScript 4.4 and earlier.
// Error!
//
// [number | Promise<100>, number | Promise<200>]
//
// is not assignable to type
//
// [number, number]
return result;
}
Now Promise.all leverages the combination of certain features with Awaited to give much better infer-
ence results, and the above example works.
For more information, you can read about this change on GitHub.
As an example, the following used to fail, but now successfully type-checks in TypeScript 4.5.
743
typescript
For more information, see the change that enables this feature.
module es2022
Thanks to Kagami S. Rosylight, TypeScript now supports a new module setting: es2022 . The main feature
in module es2022 is top-level await , meaning you can use await outside of async functions. This was
already supported in --module esnext (and now --module nodenext ), but es2022 is the first stable
target for this feature.
The above example is intentionally simple and useless, but there are plenty of types that are actually
useful, and unfortunately trigger our heuristics. As an example, the following TrimLeft type removes spa-
ces from the beginning of a string-like type. If given a string type that has a space at the beginning, it im-
mediately feeds the remainder of the string back into TrimLeft .
This type can be useful, but if a string has 50 leading spaces, you'll get an error.
That's unfortunate, because these kinds of types tend to be extremely useful in modeling operations on
strings - for example, parsers for URL routers. To make matters worse, a more useful type typically creates
more type instantiations, and in turn has even more limitations on input length.
744
TypeScript 4.5
But there's a saving grace: TrimLeft is written in a way that is tail-recursive in one branch. When it calls
itself again, it immediately returns the result and doesn't do anything with it. Because these types don't
need to create any intermediate results, they can be implemented more quickly and in a way that avoids
triggering many of type recursion heuristics that are built into TypeScript.
That's why TypeScript 4.5 performs some tail-recursion elimination on conditional types. As long as one
branch of a conditional type is simply another conditional type, TypeScript can avoid intermediate instantia-
tions. There are still heuristics to ensure that these types don't go off the rails, but they are much more
generous.
Keep in mind, the following type won't be optimized, since it uses the result of a conditional type by adding
it to a union.
type GetChars<S> =
S extends `${infer Char}${infer Rest}` ? Char | GetChars<Rest> : never;
If you would like to make it tail-recursive, you can introduce a helper that takes an "accumulator" type pa-
rameter, just like with tail-recursive functions.
eval("console.log(new Animal().isDangerous())");
By default, TypeScript always removes this import because it appears to be unused. In TypeScript 4.5, you
can enable a new flag called preserveValueImports to prevent TypeScript from stripping out any import-
ed values from your JavaScript outputs. Good reasons to use eval are few and far between, but something
very similar to this happens in Svelte:
745
typescript
</script>
These frameworks generate some code based on markup outside of their <script> tags, but TypeScript
only sees code within the <script> tags. That means TypeScript will automatically drop the import of
someFunc , and the above code won't be runnable! With TypeScript 4.5, you can use preserveValueIm‐
ports to avoid these situations.
Note that this flag has a special requirement when combined with --isolatedModules`: imported types must
be marked as type-only because compilers that process single files at a time have no way of knowing
whether imports are values that appear unused, or a type that must be removed in order to avoid a runtime
crash.
That makes another TypeScript 4.5 feature, type modifiers on import names, especially important.
When these options are combined, we need a way to signal when an import can be legitimately dropped.
TypeScript already has something for this with import type :
This works, but it would be nice to avoid two import statements for the same module. That's part of why
TypeScript 4.5 allows a type modifier on individual named imports, so that you can mix and match as
needed.
746
TypeScript 4.5
In the above example, BaseType is always guaranteed to be erased and someFunc will be preserved under
preserveValueImports , leaving us with the following code:
class Person {
#name: string;
constructor(name: string) {
this.#name = name;
}
equals(other: unknown) {
return other &&
typeof other === "object" &&
#name in other && // <- this is new!
this.#name === other.#name;
}
}
One interesting aspect of this feature is that the check #name in other implies that other must have
been constructed as a Person , since there's no other way that field could be present. This is actually one
of the key features of the proposal, and it's why the proposal is named "ergonomic brand checks" - because
private fields often act as a "brand" to guard against objects that aren't instances of their class. As such,
TypeScript is able to appropriately narrow the type of other on each check, until it ends up with the type
Person .
We'd like to extend a big thanks to our friends at Bloomberg who contributed this pull request: Ashley
Claymore, Titian Cernicova-Dragomir, Kubilay Kahveci, and Rob Palmer!
747
typescript
Import Assertions
TypeScript 4.5 supports an ECMAScript proposal for import assertions. This is a syntax used by runtimes to
make sure that an import has an expected format.
The contents of these assertions are not checked by TypeScript since they're host-specific, and are simply
left alone so that browsers and runtimes can handle them (and possibly error).
Dynamic import() calls can also use import assertions through a second argument.
The expected type of that second argument is defined by a new type called ImportCallOptions , and cur-
rently only accepts an assert property.
Previously this function was only used on Linux, but in TypeScript 4.5 it has been adopted to operating sys-
tems that are typically case-insensitive, like Windows and MacOS. On certain codebases, this change sped
up project loading by 5-13% (depending on the host operating system).
For more information, see the original change here, along with the 4.5-specific changes here.
748
TypeScript 4.5
Snippet completions for JSX attributes. For a string property, quotes are automatically added. For a numeric
properties, braces are added.
TypeScript will typically use the type of an attribute to figure out what kind of initializer to insert, but you
can customize this behavior in Visual Studio Code.
Keep in mind, this feature will only work in newer versions of Visual Studio Code, so you might have to use
an Insiders build to get this working. For more information, read up on the original pull request
In older versions of TypeScript, if the language service couldn't find a type, it would just print any .
749
typescript
Hovering over a signature where `Buffer` isn't found, TypeScript replaces it with `any`.
In the above example, Buffer wasn't found, so TypeScript replaced it with any in quick info. In
TypeScript 4.5, TypeScript will try its best to preserve what you wrote.
Hovering over a signature where `Buffer` isn't found, it continues to use the name `Buffer`.
However, if you hover over Buffer itself, you'll get a hint that TypeScript couldn't find Buffer .
Altogether, this provides a smoother experience when TypeScript doesn't have the full program available.
Keep in mind, you'll always get an error in regular scenarios to tell you when a type isn't found.
Breaking Changes
lib.d.ts Changes
TypeScript 4.5 contains changes to its built-in declaration files which may affect your compilation; however,
these changes were fairly minimal, and we expect most code will be unaffected.
750
TypeScript 4.5
Because Awaited is now used in lib.d.ts and as a result of await , you may see certain generic types
change that might cause incompatibilities; however, given many intentional design decisions around
Awaited to avoid breakage, we expect most code will be unaffected.
It's an easy mistake to accidentally forget about the compilerOptions section in a tsconfig.json . To
help catch this mistake, in TypeScript 4.5, it is an error to add a top-level field which matches any of the
available options in compilerOptions without having also defined compilerOptions in that tsconfig.j‐
son .
Go to TOC
751
typescript
class Base {
// ...
}
constructor() {
// error!
// have to call 'super()' first because it needs to initialize 'someProperty'.
doSomeStuff();
super();
}
}
This made it cheap to check that super() gets called before this is referenced, but it ended up rejecting
a lot of valid code. TypeScript 4.6 is now much more lenient in that check and permits other code to run be-
fore super() ., all while still ensuring that super() occurs at the top-level before any references to this .
We'd like to extend our thanks to Joshua Goldberg for patiently working with us to land this change!
type Action =
| { kind: "NumberContents"; payload: number }
| { kind: "StringContents"; payload: string };
This lets us work with objects that can hold different data, but a common field tells us which data those ob-
jects have.
752
TypeScript 4.6
This is very common in TypeScript; however, depending on your preferences, you might have wanted to de-
structure kind and payload in the the example above. Perhaps something like the following:
type Action =
| { kind: "NumberContents"; payload: number }
| { kind: "StringContents"; payload: string };
Previously TypeScript would error on these - once kind and payload were extracted from the same object
into variables, they were considered totally independent.
When destructuring individual properties into a const declaration, or when destructuring a parameter into
variables that are never assigned to, TypeScript will check for if the destructured type is a discriminated
union. If it is, TypeScript can now narrow the types of variables depending on checks of other variables So
in our example, a check on kind narrows the type of payload .
For more information, see the pull request that implemented this analysis.
In a structural type system, object types are compatible based on the members they have.
interface Source {
prop: string;
}
interface Target {
prop: number;
}
753
typescript
Notice that whether or not Source is compatible with Target has to do with whether their properties are
assignable. In this case, that's just prop .
When you introduce generics into this, there are some harder questions to answer. For instance, is a
Source<string> assignable to a Target<number> in the following case?
interface Source<T> {
prop: Source<Source<T>>;
}
interface Target<T> {
prop: Target<Target<T>>;
}
In order to answer that, TypeScript needs to check whether the types of prop are compatible. That leads
to the another question: is a Source<Source<string>> assignable to a Target<Target<number>> ? To an-
swer that, TypeScript checks whether prop is compatible for those types, and ends up checking whether
Source<Source<Source<string>>> is assignable to Target<Target<Target<number>>> . Keep going for a
bit, and you might notice that the type infinitely expands the more you dig in.
TypeScript has a few heuristics here - if a type appears to be infinitely expanding after encountering a cer-
tain depth check, then it considers that the types could be compatible. This is usually enough, but embar-
rassingly there were some false-negatives that this wouldn't catch.
interface Foo<T> {
prop: T;
}
x = y;
A human reader can see that x and y should be incompatible in the above example. While the types are
deeply nested, that's just a consequence of how they were declared. The heuristic was meant to capture
cases where deeply-nested types were generated through exploring the types, not from when a developer
wrote that type out themselves.
TypeScript 4.6 is now able to distinguish these cases, and correctly errors on the last example. Additionally,
because the language is no longer concerned with false-positives from explicitly-written types, TypeScript
can conclude that a type is infinitely expanding much earlier, and save a bunch of work in checking for type
compatibility. As a result, libraries on DefinitelyTyped like redux-immutable , react-lazylog , and yup
saw a 50% reduction in check-time.
You may already have this change because it was cherry-picked into TypeScript 4.5.3, but it is a notable
feature of TypeScript 4.6 which you can read up more about here.
754
TypeScript 4.6
interface TypeMap {
number: number;
string: string;
boolean: boolean;
}
This pattern was already supported and allowed TypeScript to understand that the call to
record.f(record.v) is valid, but previously the call to processRecord would give poor inference results
for val
TypeScript 4.6 improves this so that no type assertions are necessary within the call to processRecord .
What this says is that the arguments to func depends entirely on the first argument. When the first argu-
ment is the string "str" , then its second argument has to be a string . When its first argument is the
string "num" , its second argument has to be a number .
755
typescript
In cases where TypeScript infers the type of a function from a signature like this, TypeScript can now nar-
row parameters that depend on each other.
f1("a", 42);
f1("b", "hello");
--target es2022
TypeScript's --target option now supports es2022 . This means features like class fields now have a sta-
ble output target where they can be preserved. It also means that new built-in functionality like the at()
method on Array s, Object.hasOwn , or the cause option on new Error can be used either with this
new --target setting, or with --lib es2022 .
This functionality was implemented by Kagami Sascha Rosylight (saschanaz) over several PRs, and we're
grateful for that contribution!
That last void 0 argument is unnecessary in this emit mode, and removing it can improve bundle sizes.
Thanks to a pull request from Alexander Tarasyuk, TypeScript 4.6 now drops the void 0 argument.
756
TypeScript 4.6
/**
* @param x The first operand
* @param y The second operand
*/
function add(x, y) {
return x + y;
}
But what happens when these comments fall out of date? What if we rename x and y to a and b ?
/**
* @param x {number} The first operand
* @param y {number} The second operand
*/
function add(a, b) {
return a + b;
}
Previously TypeScript would only tell you about this when performing type-checking on JavaScript files -
when using either the checkJs option, or adding a // @ts-check comment to the top of your file.
You can now get similar information for TypeScript files in your editor! TypeScript now provides suggestions
for when parameter names don't match between your function and its JSDoc comment.
Suggestion diagnostics being shown in the editor for parameter names in JSDoc comments that don't match
an actual parameter name.
757
typescript
As one example, if you have two declarations of a const in the same scope of a JavaScript file, TypeScript
will now issue an error on those declarations.
// ...
As another example, TypeScript will let you know if a modifier is being incorrectly used.
function container() {
export function foo() {
// ~~~~~~
// error: Modifiers cannot appear here.
}
}
These errors can be disabled by adding a // @ts-nocheck at the top of your file, but we're interested in
hearing some early feedback about how it works for your JavaScript workflow. You can easily try it out for
Visual Studio Code by installing the TypeScript and JavaScript Nightly Extension, and read up more on the
first and second pull requests.
We recently published a tool called @typescript/analyze-trace to get a more digestible view of this informa-
tion. While we don't expect everyone to need analyze-trace , we think it can come in handy for any team
that is running into build performance issues with TypeScript.
758
TypeScript 4.6
Breaking Changes
Object Rests Drop Unspreadable Members from Generic Objects
Object rest expressions now drop members that appear to be unspreadable on generic objects. In the fol-
lowing example...
class Thing {
someProperty = 42;
someMethod() {
// ...
}
}
the variable rest used to have the type Omit<T, "someProperty"> because TypeScript would strictly an-
alyze which other properties were destructured. This doesn't model how ...rest would work in a destruc-
turing from a non-generic type because someMethod would typically be dropped as well. In TypeScript 4.6,
the type of rest is Omit<T, "someProperty" | "someMethod"> .
This can also come up in cases when destructuring from this . When destructuring this using a ...rest
element, unspreadable and non-public members are now dropped, which is consistent with destructuring in-
stances of a class in other places.
class Thing {
someProperty = 42;
someMethod() {
// ...
}
someOtherMethod() {
let { someProperty, ...rest } = this;
759
typescript
You can explicitly turn these errors off by inserting a // @ts-nocheck comment at the top of your file.
For more information, see the first and second implementing pull requests for these features.
Go to TOC
760
TypeScript 4.7
TypeScript 4.7 adds this functionality with two new module settings: node16 and nodenext .
{
"compilerOptions": {
"module": "node16",
}
}
These new modes bring a few high-level features which we'll explore here.
{
"name": "my-package",
"type": "module",
"//": "...",
"dependencies": {
}
}
This setting controls whether .js files are interpreted as ES modules or CommonJS modules, and defaults
to CommonJS when not set. When a file is considered an ES module, a few different rules come into play
compared to CommonJS:
761
typescript
To overlay the way TypeScript works in this system, .ts and .tsx files now work the same way. When
TypeScript finds a .ts , .tsx , .js , or .jsx file, it will walk up looking for a package.json to see
whether that file is an ES module, and use that to determine:
When a .ts file is compiled as an ES module, ECMAScript import / export statements are left alone in
the .js output; when it's compiled as a CommonJS module, it will produce the same output you get today
under --module commonjs .
This also means paths resolve differently between .ts files that are ES modules and ones that are CJS
modules. For example, let's say you have the following code today:
// ./foo.ts
export function helper() {
// ...
}
// ./bar.ts
import { helper } from "./foo"; // only works in CJS
helper();
This code works in CommonJS modules, but will fail in ES modules because relative import paths need to
use extensions. As a result, it will have to be rewritten to use the extension of the output of foo.ts - so
bar.ts will instead have to import from ./foo.js .
// ./bar.ts
import { helper } from "./foo.js"; // works in ESM & CJS
helper();
This might feel a bit cumbersome at first, but TypeScript tooling like auto-imports and path completion will
typically just do this for you.
One other thing to mention is the fact that this applies to .d.ts files too. When TypeScript finds a .d.ts
file in package, it is interpreted based on the containing package.
Node.js supports two extensions to help with this: .mjs and .cjs . .mjs files are always ES modules,
and .cjs files are always CommonJS modules, and there's no way to override these.
In turn, TypeScript supports two new source file extensions: .mts and .cts . When TypeScript emits these
to JavaScript files, it will emit them to .mjs and .cjs respectively.
762
TypeScript 4.7
Furthermore, TypeScript also supports two new declaration file extensions: .d.mts and .d.cts . When
TypeScript generates declaration files for .mts and .cts , their corresponding extensions will be .d.mts
and .d.cts .
Using these extensions is entirely optional, but will often be useful even if you choose not to use them as
part of your primary workflow.
CommonJS Interoperability
Node.js allows ES modules to import CommonJS modules as if they were ES modules with a default export.
// ./foo.cts
export function helper() {
console.log("hello world!");
}
// ./bar.mts
import foo from "./foo.cjs";
In some cases, Node.js also synthesizes named exports from CommonJS modules, which can be more con-
venient. In these cases, ES modules can use a "namespace-style" import (i.e. import * as foo from
"..." ), or named imports (i.e. import { helper } from "..." ).
// ./foo.cts
export function helper() {
console.log("hello world!");
}
// ./bar.mts
import { helper } from "./foo.cjs";
There isn't always a way for TypeScript to know whether these named imports will be synthesized, but
TypeScript will err on being permissive and use some heuristics when importing from a file that is definitely
a CommonJS module.
In a CommonJS module, this just boils down to a require() call, and in an ES module, this imports cre‐
ateRequire to achieve the same thing. This will make code less portable on runtimes like the browser
(which don't support require() ), but will often be useful for interoperability. In turn, you can write the
above example using this syntax as follows:
// ./foo.cts
export function helper() {
console.log("hello world!");
763
typescript
// ./bar.mts
import foo = require("./foo.cjs");
foo.helper()
Finally, it's worth noting that the only way to import ESM files from a CJS module is using dynamic
import() calls. This can present challenges, but is the behavior in Node.js today.
Here's an package.json that supports separate entry-points for CommonJS and ESM:
// package.json
{
"name": "my-package",
"type": "module",
"exports": {
".": {
// Entry-point for `import "my-package"` in ESM
"import": "./esm/index.js",
There's a lot to this feature, which you can read more about on the Node.js documentation. Here we'll try to
focus on how TypeScript supports it.
With TypeScript's original Node support, it would look for a "main" field, and then look for declaration files
that corresponded to that entry. For example, if "main" pointed to ./lib/index.js , TypeScript would
look for a file called ./lib/index.d.ts . A package author could override this by specifying a separate field
called "types" (e.g. "types": "./types/index.d.ts" ).
The new support works similarly with import conditions. By default, TypeScript overlays the same rules with
import conditions - if you write an import from an ES module, it will look up the import field, and from a
CommonJS module, it will look at the require field. If it finds them, it will look for a corresponding decla-
ration file. If you need to point to a different location for your type declarations, you can add a "types"
import condition.
764
TypeScript 4.7
// package.json
{
"name": "my-package",
"type": "module",
"exports": {
".": {
// Entry-point for `import "my-package"` in ESM
"import": {
// Where TypeScript will look.
"types": "./types/esm/index.d.ts",
Note that the "types" condition should always come first in "exports" .
TypeScript also supports the "imports" field of package.json in a similar manner by looking for declara-
tion files alongside corresponding files, and supports packages self-referencing themselves. These features
are generally not as involved to set up, but are supported.
765
typescript
This doesn't quite match up with the behavior of Node.js where the package.json can change the format
of a file, or the --jsx setting react-jsx , where any JSX file contains an implicit import to a JSX factory.
It also doesn't match modern expectations where most new TypeScript code is written with modules in
mind.
That's why TypeScript 4.7 introduces a new option called moduleDetection . moduleDetection can take
on 3 values: "auto" (the default), "legacy" (the same behavior as 4.6 and prior), and "force" .
Under the mode "auto" , TypeScript will not only look for import and export statements, but it will also
check whether
the "type" field in package.json is set to "module" when running under --module nodenext / --
module node16 , and
check whether the current file is a JSX file when running under --jsx react-jsx
In cases where you want every file to be treated as a module, the "force" setting ensures that every non-
declaration file is treated as a module. This will be true regardless of how module , moduleResoluton , and
jsx are configured.
Meanwhile, the "legacy" option simply goes back to the old behavior of only seeking out import and
export statements to determine whether a file is a module.
You can read up more about this change on the pull request.
const obj = {
[key]: numberOrString,
};
Previously, TypeScript would not consider any type guards on obj[key] , and would have no idea that
obj[key] was really a string . Instead, it would think that obj[key] was still a string | number and
accessing toUpperCase() would trigger an error.
This also means that under --strictPropertyInitialization , TypeScript can correctly check that com-
puted properties are initialized by the end of a constructor body.
766
TypeScript 4.7
class C {
[key]: string;
constructor(str: string) {
// oops, forgot to set 'this[key]'
}
screamString() {
return this[key].toUpperCase();
}
}
Under TypeScript 4.7, --strictPropertyInitialization reports an error telling us that the [key] prop-
erty wasn't definitely assigned by the end of the constructor.
We'd like to extend our gratitude to Oleksandr Tarasiuk who provided this change!
// Works
f({
produce: () => "hello",
consume: x => x.toLowerCase()
});
// Works
f({
produce: (n: string) => n,
consume: x => x.toLowerCase(),
});
767
typescript
Inference failed in some of these examples because knowing the type of their produce functions would in-
directly request the type of arg before finding a good type for T . TypeScript now gathers functions that
could contribute to the inferred type of T and infers from them lazily.
For more information, you can take a look at the specific modifications to our inference process.
Instantiation Expressions
Occasionally functions can be a bit more general than we want. For example, let's say we had a makeBox
function.
interface Box<T> {
value: T;
}
function makeBox<T>(value: T) {
return { value };
}
Maybe we want to create a more specialized set of functions for making Box es of Wrench es and Hammer s.
To do that today, we'd have to wrap makeBox in other functions, or use an explicit type for an alias of
makeBox .
// or...
These work, but wrapping a call to makeBox is a bit wasteful, and writing the full signature of makeWrench‐
Box could get unwieldy. Ideally, we would be able to say that we just want to alias makeBox while replac-
ing all of the generics in its signature.
TypeScript 4.7 allows exactly that! We can now take functions and constructors and feed them type argu-
ments directly.
So with this, we can specialize makeBox to accept more specific types and reject anything else.
This logic also works for constructor functions such as Array , Map , and Set .
768
TypeScript 4.7
When a function or constructor is given type arguments, it will produce a new type that keeps all signatures
with compatible type parameter lists, and replaces the corresponding type parameters with the given type
arguments. Any other signatures are dropped, as TypeScript will assume that they aren't meant to be used.
For more information on this feature, check out the pull request.
type FirstIfString<T> =
T extends [infer S, ...unknown[]]
? S extends string ? S : never
: never;
// string
type A = FirstIfString<[string, number, number]>;
// "hello"
type B = FirstIfString<["hello", number, number]>;
// "hello" | "world"
type C = FirstIfString<["hello" | "world", boolean]>;
// never
type D = FirstIfString<[boolean, number, string]>;
FirstIfString matches against any tuple with at least one element and grabs the type of the first ele-
ment as S . Then it checks if S is compatible with string and returns that type if it is.
Note that we had to use two conditional types to write this. We could have written FirstIfString as
follows:
type FirstIfString<T> =
T extends [string, ...unknown[]]
// Grab the first type out of `T`
? T[0]
: never;
This works, but it's slightly more "manual" and less declarative. Instead of just pattern-matching on the
type and giving the first element a name, we have to fetch out the 0 th element of T with T[0] . If we
were dealing with types more complex than tuples, this could get a lot trickier, so infer can simplify
things.
769
typescript
Using nested conditionals to infer a type and then match against that inferred type is pretty common. To
avoid that second level of nesting, TypeScript 4.7 now allows you to place a constraint on any infer type.
type FirstIfString<T> =
T extends [infer S extends string, ...unknown[]]
? S
: never;
This way, when TypeScript matches against S , it also ensures that S has to be a string . If S isn't a
string , it takes the false path, which in these cases is never .
interface Animal {
animalStuff: any;
}
// ...
Imagine we had two different instances of Getter s. Figuring out whether any two different Getter s are
substitutable for one another depends entirely on T . In the case of whether an assignment of
Getter<Dog> → Getter<Animal> is valid, we have to check whether Dog → Animal is valid. Because
each type for T just gets related in the same "direction", we say that the Getter type is covariant on T .
On the other hand, checking whether Setter<Dog> → Setter<Animal> is valid involves checking whether
Animal → Dog is valid. That "flip" in direction is kind of like how in math, checking whether −x < −y is
the same as checking whether y < x. When we have to flip directions like this to compare T , we say that
Setter is contravariant on T .
With TypeScript 4.7, we're now able to explicitly specify variance on type parameters.
So now, if we want to make it explicit that Getter is covariant on T , we can now give it an out modifier.
And similarly, if we also want to make it explicit that Setter is contravariant on T , we can give it an in
modifier.
770
TypeScript 4.7
out and in are used here because a type parameter's variance depends on whether it's used in an output
or an input. Instead of thinking about variance, you can just think about if T is used in output and input
positions.
When a T is used in both an output and input position, it becomes invariant. Two different State<T> s
can't be interchanged unless their T s are the same. In other words, State<Dog> and State<Animal>
aren't substitutable for the other.
Now technically speaking, in a purely structural type system, type parameters and their variance don't real-
ly matter - you can just plug in types in place of each type parameter and check whether each matching
member is structurally compatible. So if TypeScript uses a structural type system, why are we interested in
the variance of type parameters? And why might we ever want to annotate them?
One reason is that it can be a useful for a reader to explicitly see how a type parameter is used at a glance.
For much more complex types, it can be difficult to tell whether a type is meant to be read, written, or both.
TypeScript will also help us out if we forget to mention how that type parameter is used. As an example, if
we forgot to specify both in and out on State , we'd get an error.
Another reason is precision and speed! TypeScript already tries to infer the variance of type parameters as
an optimization. By doing this, it can type-check larger structural types in a reasonable amount of time.
Calculating variance ahead of time allows the type-checker to skip deeper comparisons and just compare
type arguments which can be much faster than comparing the full structure of a type over and over again.
But often there are cases where this calculation is still fairly expensive, and the calculation may find circu-
larities that can't be accurately resolved, meaning there's no clear answer for the variance of a type.
type Foo<T> = {
x: T;
f: Bar<T>;
}
771
typescript
type Baz<V> = {
value: Foo<V[]>;
}
- type Foo<T> = {
+ type Foo<in out T> = {
x: T;
f: Bar<T>;
}
We don't necessarily recommend annotating every type parameter with its variance; For example, it's possi-
ble (but not recommended) to make variance a little stricter than is necessary, so TypeScript won't stop you
from marking something as invariant if it's really just covariant, contravariant, or even independent. So if
you do choose to add explicit variance markers, we would encourage thoughtful and precise use of them.
But if you're working with deeply recursive types, especially if you're a library author, you may be interested
in using these annotations to the benefit of your users. Those annotations can provide wins in both accuracy
and type-checking speed, which can even affect their code editing experience. Determining when variance
calculation is a bottleneck on type-checking time can be done experimentally, and determined using tooling
like our analyze-trace utility.
For more details on this feature, you can read up on the pull request.
{
"compilerOptions": {
"moduleSuffixes": [".ios", ".native", ""]
}
}
will try to look at the relative files ./foo.ios.ts , ./foo.native.ts , and finally ./foo.ts .
Note that the empty string "" in moduleSuffixes is necessary for TypeScript to also look-up ./foo.ts .
In a sense, the default value for moduleSuffixes is [""] .
772
TypeScript 4.7
This feature can be useful for React Native projects where each target platform can use a separate tscon‐
fig.json with differing moduleSuffixes .
resolution-mode
With Node's ECMAScript resolution, the mode of the containing file and the syntax you use determines how
imports are resolved; however it would be useful to reference the types of a CommonJS module from an
ECMAScript module, or vice-versa.
// or
Additionally, in nightly versions of TypeScript, import type can specify an import assertion to achieve
something similar.
The import type and import() syntaxes only support resolution-mode in nightly builds of TypeScript.
You'll likely get an error like
Resolution mode assertions are unstable. Use nightly TypeScript to silence this
error. Try updating with 'npm install -D typescript@next'.
If you do find yourself using this feature in nightly versions of TypeScript, consider providing feedback on
this issue.
773
typescript
You can see the respective changes for reference directives and for type import assertions.
Go to Source Definition
TypeScript 4.7 contains support for a new experimental editor command called Go To Source Definition. It's
similar to Go To Definition, but it never returns results inside declaration files. Instead, it tries to find corre-
sponding implementation files (like .js or .ts files), and find definitions there — even if those files are
normally shadowed by .d.ts files.
This comes in handy most often when you need to peek at the implementation of a function you're import-
ing from a library instead of its type declaration in a .d.ts file.
The "Go to Source Definition" command on a use of the yargs package jumps the editor to an index.cjs file
in yargs.
You can try this new command in the latest versions of Visual Studio Code. Note, though, that this function-
ality is still in preview, and there are some known limitations. In some cases TypeScript uses heuristics to
guess which .js file corresponds to the given result of a definition, so these results might be inaccurate.
Visual Studio Code also doesn't yet indicate whether a result was a guess, but it's something we're collabo-
rating on.
You can leave feedback about the feature, read about known limitations, or learn more at our dedicated
feedback issue.
774
TypeScript 4.7
// local code
import * as bbb from "./bbb";
import * as ccc from "./ccc";
import * as aaa from "./aaa";
// built-ins
import * as path from "path";
import * as child_process from "child_process"
import * as fs from "fs";
// some code...
// local code
import * as child_process from "child_process";
import * as fs from "fs";
// built-ins
import * as path from "path";
import * as aaa from "./aaa";
import * as bbb from "./bbb";
import * as ccc from "./ccc";
// some code...
This is... not ideal. Sure, our imports are sorted by their paths, and our comments and newlines are pre-
served, but not in a way we expected. Much of the time, if we have our imports grouped in a specific way,
then we want to keep them that way.
TypeScript 4.7 performs Organize Imports in a group-aware manner. Running it on the above code looks a
little bit more like what you'd expect:
// local code
import * as aaa from "./aaa";
import * as bbb from "./bbb";
import * as ccc from "./ccc";
// built-ins
import * as child_process from "child_process";
import * as fs from "fs";
import * as path from "path";
// some code...
We'd like to extend our thanks to Minh Quy who provided this feature.
775
typescript
Breaking Changes
lib.d.ts Updates
While TypeScript strives to avoid major breaks, even small changes in the built-in libraries can cause issues.
We don't expect major breaks as a result of DOM and lib.d.ts updates, but there may be some small
ones.
interface Props {
stuff?: string;
}
776
TypeScript 4.7
This makes this behavior more consistent with spreads in object literals.
As a result, TypeScript will issue an error as well; however, TypeScript now also checks if a generic value
that is constrained to a symbol in some way is used in a template string.
In some cases, you can get around this by wrapping the expression in a call to String , just like the error
message suggests.
In others, this error is too pedantic, and you might not ever care to even allow symbol keys when using
keyof . In such cases, you can switch to string & keyof ... :
777
typescript
For more information, you can see the implementing pull request.
Go to TOC
778
TypeScript 4.8
For example, unknown is close in spirit to the union type {} | null | undefined because it accepts
null , undefined , and any other type. TypeScript now recognizes this, and allows assignments from un‐
known to {} | null | undefined .
Another change is that {} intersected with any other object type simplifies right down to that object type.
That meant that we were able to rewrite NonNullable to just use an intersection with {} , because {} &
null and {} & undefined just get tossed away.
This is an improvement because intersection types like this can be reduced and assigned to, while condi-
tional types currently cannot. So NonNullable<NonNullable<T>> now simplifies at least to
NonNullable<T> , whereas it didn't before.
These changes also allowed us to bring in sensible improvements in control flow analysis and type narrow-
ing. For example, unknown is now narrowed just like {} | null | undefined in truthy branches.
779
typescript
Generic values also get narrowed similarly. When checking that a value isn't null or undefined ,
TypeScript now just intersects it with {} - which again, is the same as saying it's NonNullable . Putting
many of the changes here together, we can now define the following function without any type assertions.
value now gets narrowed to T & {} , and is now identical with NonNullable<T> - so the body of the
function just works with no TypeScript-specific syntax.
On their own, these changes may appear small - but they represent fixes for many many paper cuts that
have been reported over several years.
For more specifics on these improvements, you can read more here.
If these infer types appear in a template string type and are constrained to a primitive type, TypeScript
will now try to parse out a literal type.
This can now better convey what a library will do at runtime, and give more precise types.
One note on this is that when TypeScript parses these literal types out it will greedily try to parse out as
much of what looks like of the appropriate primitive type; however it then checks to see if the print-back of
that primitive matches up with the string contents. In other words, TypeScript checks whether the going
from the string, to the primitive, and back matches. If it doesn't see that the string can be "round-tripped",
then it will fall back to the base primitive type.
780
TypeScript 4.8
How big are these improvements? Well, on a fairly large internal codebase, we've seen time reductions on
the order of 10%-25% on many simple common operations, with around 40% time reductions in no-change
scenarios. We've seen similar results on the TypeScript codebase as well.
You can see the changes, along with the performance results on GitHub.
if people_at_home == []:
print("here's where I lie, broken inside. </3")
adopt_animals()
This is not the case in JavaScript, where == and === between objects (and therefore, arrays) check
whether both references point to the same value. We believe that similar code in JavaScript is at best an
early foot-gun for JavaScript developers, and at worst a bug in production code. That's why TypeScript now
disallows code like the following.
We'd like to extend our gratitude to Jack Works who contributed this check. You can view the changes in-
volved here.
781
typescript
When chooseRandomly needs to figure out a type for T , it will primarily look at [42, true, "hi!"] and
[0, false, "bye!"] ; but TypeScript needs to figure out whether those two types should be
Array<number | boolean | string> or the tuple type [number, boolean, string] . To do that, it will
look for existing candidates as a hint to see whether there are any tuple types. When TypeScript sees the
binding pattern [a, b, c] , it creates the type [any, any, any] , and that type gets picked up as a low-
priority candidate for T which also gets used as a hint for the types of [42, true, "hi!"] and [0,
false, "bye!"] .
You can see how this was good for chooseRandomly , but it fell short in other cases. For example, take the
following code
The binding pattern [x, y, z] hinted that f should produce an [any, any, any] tuple; but f really
shouldn't change its type argument based on a binding pattern. It can't suddenly conjure up a new array-
like value based on what it's being assigned to, so the binding pattern type has way too much influence on
the produced type. On top of that, because the binding pattern type is full of any s, we're left with x , y ,
and z being typed as any .
In TypeScript 4.8, these binding patterns are never used as candidates for type arguments. Instead, they're
just consulted in case a parameter needs a more specific type like in our chooseRandomly example. If you
need to revert to the old behavior, you can always provide explicit type arguments.
You can look at the change on GitHub if you're curious to learn more.
782
TypeScript 4.8
This was caused by assumptions of how Node.js handles rename events across file systems. File systems
used by Linux and macOS utilize inodes, and Node.js will attach file watchers to inodes rather than file
paths. So when Node.js returns a watcher object, it might be watching a path or an inode depending on the
platform and file system.
To be a bit more efficient, TypeScript tries to reuse the same watcher objects if it detects a path still exists
on disk. This is where things went wrong, because even if a file still exists at that path, a distinct file might
have been created, and that file will have a different inode. So TypeScript would end up reusing the watcher
object instead of installing a new watcher at the original location, and watch for changes at what might be a
totally irrelevant file. So TypeScript 4.8 now handles these cases on inode systems and properly installs a
new watcher and fixes this.
We'd like to extend our thanks to Marc Celani and his team at Airtable who invested lots of time in investi-
gating the issues they were experiencing and pointing out the root cause. You can view the specific fixes
around file-watching here.
{
// Note that `javascript.preferences.autoImportFileExcludePatterns` can be
specified for JavaScript too.
"typescript.preferences.autoImportFileExcludePatterns": [
"**/node_modules/@types/node"
]
}
This can be useful in cases where you can't avoid having certain modules or libraries in your compilation but
you rarely want to import from them. These modules might have lots of exports that can pollute the auto-
imports list and make it harder to navigate, and this option can help in those situations.
783
typescript
lib.d.ts Updates
While TypeScript strives to avoid major breaks, even small changes in the built-in libraries can cause issues.
We don't expect major breaks as a result of DOM and lib.d.ts updates, but one notable change is that
the cause property on Error s now has the type unknown instead of Error .
foo(undefined);
As demonstrated above, code like this has a potential bug - the values null and undefined can be indi-
rectly passed through these unconstrained type parameters to code that is not supposed to observe those
values.
This behavior will also be visible in type positions. One example would be:
interface Foo<T> {
x: Bar<T>;
}
Existing code that didn't want to handle null and undefined can be fixed by propagating the appropriate
constraints through.
- function foo<T>(x: T) {
+ function foo<T extends {}>(x: T) {
function foo<T>(x: T) {
+ if (x !== null && x !== undefined) {
bar(x);
+ }
}
784
TypeScript 4.8
And if you know that for some reason, your generic value can't be null or undefined , you can just use a
non-null assertion.
function foo<T>(x: T) {
- bar(x);
+ bar(x!);
}
When it comes to types, you'll often either need to propagate constraints, or intersect your types with {} .
For more information, you can see the change that introduced this along with the specific discussion issue
regarding how unconstrained generics now work.
@decorator
export class Foo {
// ...
}
Decorators as currently proposed do not support this syntax. Instead, the export keyword must precede
the decorator.
Unfortunately, TypeScript's trees are concrete rather than abstract, and our architecture expects syntax tree
node fields to be entirely ordered before or after each other. To support both legacy decorators and decora-
tors as proposed, TypeScript will have to gracefully parse, and intersperse, modifiers and decorators.
To do this, it exposes a new type alias called ModifierLike which is a Modifier or a Decorator .
Decorators are now placed in the same field as modifiers which is now a NodeArray<ModifierLike>
when set, and the entire field is deprecated.
785
typescript
All existing decorators properties have been marked as deprecated and will always be undefined if
read. The type has also been changed to undefined so that existing tools know to handle them correctly.
To avoid new deprecation warnings and other issues, TypeScript now exposes four new functions to use in
place of the decorators and modifiers properties. There are individual predicates for testing whether a
node has support modifiers and decorators, along with respective accessor functions for grabbing them.
With the note that each call to getModifiers and getDecorators may allocate a new array.
// @ts-check
/**
* @type {SomeType}
*/
export const myValue = someValue;
/**
* @typedef {string | number} MyType
*/
786
TypeScript 4.8
To reference a type from another module, you can instead directly qualify the import.
/**
- * @type {SomeType}
+ * @type {import("some-module").SomeType}
*/
export const myValue = someValue;
To export a type, you can just use a /** @typedef */ comment in JSDoc. @typedef comments already
automatically export types from their containing modules.
/**
* @typedef {string | number} MyType
*/
+ /**
+ * @typedef {MyType} MyExportedType
+ */
- export { MyType as MyExportedType };
You might read this signature and think that makePerson obviously takes an object with a name property
with the type string and an age property with the type number ; however, JavaScript's destructuring
syntax is actually taking precedence here. makePerson does say that it's going to take an object with a
name and an age property, but instead of specifying a type for them, it's just saying that it renames name
and age to string and number respectively.
In a pure type construct, writing code like this is useless, and typically a mistake since developers usually
assume they're writing a type annotation.
TypeScript 4.8 makes these an error unless they're referenced later in the signature. The correct way to
write the above signature would be as follows:
787
typescript
// or
declare function makePerson({ name, age }: { name: string, age: number }): Person;
This change can catch bugs in declarations, and has been helpful for improving existing code. We'd like to
extend our thanks to GitHub user uhyo for providing this check. You can read up on the change here.
Go to TOC
788
TypeScript 4.9
For example:
Notice that we've written bleu , whereas we probably should have written blue . We could try to catch that
bleu typo by using a type annotation on palette , but we'd lose the information about each property.
The new satisfies operator lets us validate that the type of an expression matches some type, without
changing the resulting type of that expression. As an example, we could use satisfies to validate that all
the properties of palette are compatible with string | number[] :
const palette = {
red: [255, 0, 0],
green: "#00ff00",
bleu: [0, 0, 255]
// ~~~~ The typo is now caught!
} satisfies Record<Colors, string | RGB>;
789
typescript
satisfies can be used to catch lots of possible errors. For example, we could ensure that an object has all
the keys of some type, but no more:
// All the information about the 'red', 'green', and 'blue' properties are
retained.
const g: boolean = favoriteColors.green;
Maybe we don't care about if the property names match up somehow, but we do care about the types of
each property. In that case, we can also ensure that all of an object's property values conform to some
type.
const palette = {
red: [255, 0, 0],
green: "#00ff00",
blue: [0, 0]
// ~~~~~~ error!
} satisfies Record<string, string | RGB>;
For more examples, you can see the issue proposing this and the implementing pull request. We'd like to
thank Oleksandr Tarasiuk who implemented and iterated on this feature with us.
Previously, TypeScript allowed us to narrow away any types that don't explicitly list a property.
interface RGB {
red: number;
green: number;
blue: number;
}
interface HSV {
hue: number;
saturation: number;
790
TypeScript 4.9
value: number;
}
Here, the type RGB didn't list the hue and got narrowed away, and leaving us with the type HSV .
But what about examples where no type listed a given property? In those cases, the language didn't help us
much. Let's take the following example in JavaScript:
function tryGetPackageName(context) {
const packageJSON = context.packageJSON;
// Check to see if we have an object.
if (packageJSON && typeof packageJSON === "object") {
// Check to see if it has a string name property.
if ("name" in packageJSON && typeof packageJSON.name === "string") {
return packageJSON.name;
}
}
return undefined;
}
Rewriting this to canonical TypeScript would just be a matter of defining and using a type for context ;
however, picking a safe type like unknown for the packageJSON property would cause issues in older ver-
sions of TypeScript.
interface Context {
packageJSON: unknown;
}
return undefined;
}
This is because while the type of packageJSON was narrowed from unknown to object , the in operator
strictly narrowed to types that actually defined the property being checked. As a result, the type of pack‐
ageJSON remained object .
791
typescript
TypeScript 4.9 makes the in operator a little bit more powerful when narrowing types that don't list the
property at all. Instead of leaving them as-is, the language will intersect their types with
Record<"property-key-being-checked", unknown> .
So in our example, packageJSON will have its type narrowed from unknown to object to object &
Record<"name", unknown> That allows us to access packageJSON.name directly and narrow that
independently.
interface Context {
packageJSON: unknown;
}
return undefined;
}
TypeScript 4.9 also tightens up a few checks around how in is used, ensuring that the left side is as-
signable to the type string | number | symbol , and the right side is assignable to object . This helps
check that we're using valid property keys, and not accidentally checking primitives.
Auto-Accessors in Classes
TypeScript 4.9 supports an upcoming feature in ECMAScript called auto-accessors. Auto-accessors are de-
clared just like properties on classes, except that they're declared with the accessor keyword.
Under the covers, these auto-accessors "de-sugar" to a get and set accessor with an unreachable private
property.
You can [read up more about the auto-accessors pull request on the original PR]
(https://github.com/microsoft/TypeScript/pull/49705).
792
TypeScript 4.9
For some background, NaN is a special numeric value that stands for "Not a Number". Nothing is ever equal
to NaN - even NaN !
console.log(NaN == 0) // false
console.log(NaN === 0) // false
console.log(NaN != 0) // true
console.log(NaN !== 0) // true
This technically isn't a JavaScript-specific problem, since any language that contains IEEE-754 floats has the
same behavior; but JavaScript's primary numeric type is a floating point number, and number parsing in
JavaScript can often result in NaN . In turn, checking against NaN ends up being fairly common, and the
correct way to do so is to use [ Number.isNaN ](https://developer.mozilla.org/en-
US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN) - but as we mentioned, lots of people
accidentally end up checking with someValue === NaN instead.
TypeScript now errors on direct comparisons against NaN , and will suggest using some variation of
Number.isNaN instead.
We believe that this change should strictly help catch beginner errors, similar to how TypeScript currently
issues errors on comparisons against object and array literals.
793
typescript
get a polling file-watcher. While polling tends to be more predictable across platforms and file systems, it
means that your CPU has to periodically get interrupted and check for updates to the file, even when noth-
ing's changed. For a few dozen files, this might not be noticeable; but on a bigger project with lots of files -
or lots of files in node_modules - this can become a resource hog.
Generally speaking, a better approach is to use file system events. Instead of polling, we can announce that
we're interested in updates of specific files and provide a callback for when those files actually do change.
Most modern platforms in use provide facilities and APIs like CreateIoCompletionPort , kqueue , epoll ,
and inotify . Node.js mostly abstracts these away by providing [ fs.watch ](https://nodejs.org/docs/lat-
est-v18.x/api/fs.html#fswatchfilename-options-listener). File system events usually work great, but there
are [lots of caveats](https://nodejs.org/docs/latest-v18.x/api/fs.html#caveats) to using them, and in turn,
to using the fs.watch API. A watcher needs to be careful to consider [inode watching]
(https://nodejs.org/docs/latest-v18.x/api/fs.html#inodes), [unavailability on certain file systems]
(https://nodejs.org/docs/latest-v18.x/api/fs.html#availability) (e.g.networked file systems), whether recur-
sive file watching is available, whether directory renames trigger events, and even file watcher exhaustion!
In other words, it's not quite a free lunch, especially if you're looking for something cross-platform.
As a result, our default was to pick the lowest common denominator: polling. Not always, but most of the
time.
Over time, we've provided the means to [choose other file-watching strategies](https://www.typescript-
lang.org/docs/handbook/configuring-watch.html). This allowed us to get feedback and harden our file-
watching implementation against most of these platform-specific gotchas. As TypeScript has needed to scale
to larger codebases, and has improved in this area, we felt swapping to file system events as the default
would be a worthwhile investment.
In TypeScript 4.9, file watching is powered by file system events by default, only falling back to polling if we
fail to set up event-based watchers. For most developers, this should provide a much less resource-inten-
sive experience when running in --watch mode, or running with a TypeScript-powered editor like Visual
Studio or VS Code.
794
TypeScript 4.9
The first was called "Organize Imports" which would remove unused imports, and then sort the remaining
ones. It would rewrite that file to look like this one:
In TypeScript 4.3, we introduced a command called "Sort Imports" which would only sort imports in the file,
but not remove them - and would rewrite the file like this.
The caveat with "Sort Imports" was that in Visual Studio Code, this feature was only available as an on-save
command - not as a manually triggerable command.
TypeScript 4.9 adds the other half, and now provides "Remove Unused Imports". TypeScript will now re-
move unused import names and statements, but will otherwise leave the relative ordering alone.
This feature is available to all editors that wish to use either command; but notably, Visual Studio Code
(1.73 and later) will have support built in and will surface these commands via its Command Palette. Users
who prefer to use the more granular "Remove Unused Imports" or "Sort Imports" commands should be able
to reassign the "Organize Imports" key combination to them if desired.
795
typescript
We expect TypeScript will expand this functionality to more keywords [such as await and yield ]
(https://github.com/microsoft/TypeScript/issues/51223) or [ switch , case , and default ]
(https://github.com/microsoft/TypeScript/issues/51225).
Performance Improvements
TypeScript has a few small, but notable, performance improvements.
First, TypeScript's forEachChild function has been rewritten to use a function table lookup instead of a
switch statement across all syntax nodes. forEachChild is a workhorse for traversing syntax nodes in
the compiler, and is used heavily in the binding stage of our compiler, along with parts of the language ser-
vice. The refactoring of forEachChild yielded up to a 20% reduction of time spent in our binding phase
and across language service operations.
Once we discovered this performance win for forEachChild , we tried it out on visitEachChild , a func-
tion we use for transforming nodes in the compiler and language service. The same refactoring yielded up
to a 3% reduction in time spent in generating project output.
Finally, the way TypeScript preserves the information about a type in the true branch of a conditional type
has been optimized. In a type like
TypeScript has to "remember" that A must also be an Animal when checking if Zoo<A> is valid. This is
basically done by creating a special type that used to hold the intersection of A with Animal ; however,
TypeScript previously did this eagerly which isn't always necessary. Furthermore, some faulty code in our
type-checker prevented these special types from being simplified. TypeScript now defers intersecting these
types until it's necessary. For codebases with heavy use of conditional types, you might witness significant
speed-ups with TypeScript, but in our performance testing suite, we saw a more modest 3% reduction in
type-checking time.
You can read up more on these optimizations on their respective pull requests:
[ forEachChild as a jump-table](https://github.com/microsoft/TypeScript/pull/50225)
[ visitEachChild as a jump-table](https://github.com/microsoft/TypeScript/pull/50266)
796
TypeScript 4.9
This behavior was questionable, especially the detection of whether the import doesn't refer to a value,
since it means that TypeScript has to trust sometimes-inaccurate declaration files. In turn, TypeScript now
preserves imports in JavaScript files.
// Input:
import { someValue, SomeClass } from "some-module";
// Previous Output:
import { someValue } from "some-module";
// Current Output:
import { someValue, SomeClass } from "some-module";
797
typescript
{
"type": "module",
"main": "./dist/main.js"
"typesVersions": {
"<4.8": { ".": ["4.8-types/main.d.ts"] },
"*": { ".": ["modern-types/main.d.ts"] }
},
"exports": {
".": {
+ "types@<4.8": "4.8-types/main.d.ts",
+ "types": "modern-types/main.d.ts",
"import": "./dist/main.js"
}
}
}
798
ASP.NET Core
Next, if your version of Visual Studio does not already have the latest TypeScript, you can install it.
5. Name your project and solution. After select the Create button
6. In the last window, select the Empty template and press the Create button
Open up your Startup.cs file and edit your Configure function to look like this:
799
typescript
app.UseDefaultFiles();
app.UseStaticFiles();
}
You may need to restart VS for the red squiggly lines below UseDefaultFiles and UseStaticFiles to
disappear.
Add TypeScript
Next we will add a new folder and call it scripts .
The Path of "Add" then "New Folder" in Visual Studio from a Web Project
The Path of "Add" then "New Folder" in Visual Studio from a Web Project
function sayHello() {
const compiler = (document.getElementById("compiler") as HTMLInputElement)
.value;
const framework = (document.getElementById("framework") as HTMLInputElement)
.value;
return `Hello from ${compiler} and ${framework}!`;
}
First we need to tell TypeScript how to build. Right click on scripts and click New Item. Then choose
TypeScript Configuration File and use the default name of tsconfig.json
800
ASP.NET Core
A screenshot showing the new file diaglogue with TypeScript JSON Config selected
A screenshot showing the new file diaglogue with TypeScript JSON Config selected
{
"compilerOptions": {
"noEmitOnError": true,
"noImplicitAny": true,
"sourceMap": true,
"target": "es6"
},
"files": ["./app.ts"],
"compileOnSave": true
}
noImplicitAny is good idea whenever you’re writing new code — you can make sure that you don’t write
any untyped code by mistake. "compileOnSave" makes it easy to update your code in a running web app.
Set up NPM
We need to setup NPM so that JavaScript packages can be downloaded. Right click on the project and select
New Item. Then choose NPM Configuration File and use the default name of package.json .
Screenshot of VS showing new file dialog with 'npm configuration file' selected
Screenshot of VS showing new file dialog with 'npm configuration file' selected
Inside the "devDependencies" section of the package.json file, add gulp and del
"devDependencies": {
"gulp": "4.0.2",
"del": "5.1.0"
}
Visual Studio should start installing gulp and del as soon as you save the file. If not, right-click package.json
and then Restore Packages.
801
typescript
Set up gulp
Right click on the project and click New Item. Then choose JavaScript File and use the name of gulp‐
file.js
var paths = {
scripts: ["scripts/**/*.js", "scripts/**/*.ts", "scripts/**/*.map"],
};
gulp.task("clean", function () {
return del(["wwwroot/scripts/**/*"]);
});
The first line tells Visual Studio to run the task ‘default’ after the build finishes. It will also run the ‘clean’
task when you ask Visual Studio to clean the build.
Screenshot of right clicking on the "Gulpfile.js" with 'Task Runner Exploere' selected
Screenshot of right clicking on the "Gulpfile.js" with 'Task Runner Exploere' selected
If ‘default’ and ‘clean’ tasks don’t show up, refresh the explorer:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="scripts/app.js"></script>
<title></title>
</head>
802
ASP.NET Core
<body>
<div id="message"></div>
<div>
Compiler: <input id="compiler" value="TypeScript"
onkeyup="document.getElementById('message').innerText = sayHello()" /><br />
Framework: <input id="framework" value="ASP.NET"
onkeyup="document.getElementById('message').innerText = sayHello()" />
</div>
</body>
</html>
Test
1. Run the project
2. As you type on the boxes you should see the message appear/change!
Debug
1. In Edge, press F12 and click the Debugger tab.
2. Look in the first localhost folder, then scripts/app.ts
3. Put a breakpoint on the line with return.
4. Type in the boxes and confirm that the breakpoint hits in TypeScript code and that inspection works
correctly.
An image showing the debugger running the code you have just wrote
An image showing the debugger running the code you have just wrote
Congrats you've built your own .NET Core project with a TypeScript frontend.
Go to TOC
803
typescript
Angular is a modern framework built entirely in TypeScript, and as a result, using TypeScript with Angular
provides a seamless experience.
The Angular documentation not only supports TypeScript as a first-class citizen, but uses it as its primary
language. With this in mind, Angular's site will always be the most up-to-date reference for using Angular
with TypeScript.
Check out the quick start guide here to start learning Angular now!
Go to TOC
804
Using Babel with TypeScript
A lot of the time the answer is "it depends", or "someone may have decided for you" depending on the
project. If you are building your project with an existing framework like tsdx, Angular, NestJS or any frame-
work mentioned in the Getting Started then this decision is handled for you.
Is your build output mostly the same as your source input files? Use tsc
Do you need a build pipeline with multiple potential outputs? Use babel for transpiling and tsc for
type checking
This technique is a hybrid approach, using Babel's preset-typescript to generate your JS files, and then us-
ing TypeScript to do type checking and .d.ts file generation.
By using babel's support for TypeScript, you get the ability to work with existing build pipelines and are
more likely to have a faster JS emit time because Babel does not type check your code.
The downside to using babel is that you don't get type checking during the transition from TS to JS. This
can mean that type errors which you miss in your editor could sneak through into production code.
In addition to that, Babel cannot create .d.ts files for your TypeScript which can make it harder to work
with your project if it is a library.
To fix these issues, you would probably want to set up a command to type check your project using TSC.
This likely means duplicating some of your babel config into a corresponding tsconfig.json and ensuring
these flags are enabled:
"compilerOptions": {
// Ensure that .d.ts files are created by tsc, but not .js files
"declaration": true,
"emitDeclarationOnly": true,
// Ensure that Babel can safely transpile files in the TypeScript project
"isolatedModules": true
}
isolatedModules
805
typescript
declaration , emitDeclarationOnly
Go to TOC
806
DOM Manipulation
DOM Manipulation
An exploration into the HTMLElement type
In the 20+ years since its standardization, JavaScript has come a very long way. While in 2020, JavaScript
can be used on servers, in data science, and even on IoT devices, it is important to remember its most pop-
ular use case: web browsers.
Websites are made up of HTML and/or XML documents. These documents are static, they do not change.
The Document Object Model (DOM) is a programming interface implemented by browsers in order to make
static websites functional. The DOM API can be used to change the document structure, style, and content.
The API is so powerful that countless frontend frameworks (jQuery, React, Angular, etc.) have been devel-
oped around it in order to make dynamic websites even easier to develop.
TypeScript is a typed superset of JavaScript, and it ships type definitions for the DOM API. These definitions
are readily available in any default TypeScript project. Of the 20,000+ lines of definitions in lib.dom.d.ts,
one stands out among the rest: HTMLElement . This type is the backbone for DOM manipulation with
TypeScript.
You can explore the source code for the DOM type definitions
Basic Example
Given a simplified index.html file:
<!DOCTYPE html>
<html lang="en">
<head><title>TypeScript Dom Manipulation</title></head>
<body>
<div id="app"></div>
<!-- Assume index.js is the compiled output of index.ts -->
<script src="index.js"></script>
</body>
</html>
Let's explore a TypeScript script that adds a <p>Hello, World!</p> element to the #app element.
807
typescript
After compiling and running the index.html page, the resulting HTML will be:
<div id="app">
<p>Hello, World!</p>
</div>
Document.getElementById
The definition for this method is as follows:
Pass it an element id string and it will return either HTMLElement or null . This method introduces one of
the most important types, HTMLElement . It serves as the base interface for every other element interface.
For example, the p variable in the code example is of type HTMLParagraphElement . Also take note that
this method can return null . This is because the method can't be certain pre-runtime if it will be able to
actually find the specified element or not. In the last line of the code snippet, the new optional chaining op-
erator is used in order to call appendChild .
Document.createElement
The definition for this method is (I have omitted the deprecated definition):
This is an overloaded function definition. The second overload is simplest and works a lot like the getEle‐
mentById method does. Pass it any string and it will return a standard HTMLElement. This definition is
what enables developers to create unique HTML element tags.
For those interested, you can interact with custom tag elements using the document.getElementsByT
agName
808
DOM Manipulation
For the first definition of createElement , it is using some advanced generic patterns. It is best understood
broken down into chunks, starting with the generic expression: <K extends keyof
HTMLElementTagNameMap> . This expression defines a generic parameter K that is constrained to the keys
of the interface HTMLElementTagNameMap . The map interface contains every specified HTML tag name and
its corresponding type interface. For example here are the first 5 mapped values:
interface HTMLElementTagNameMap {
"a": HTMLAnchorElement;
"abbr": HTMLElement;
"address": HTMLElement;
"applet": HTMLAppletElement;
"area": HTMLAreaElement;
...
}
Some elements do not exhibit unique properties and so they just return HTMLElement , but other types do
have unique properties and methods so they return their specific interface (which will extend from or imple-
ment HTMLElement ).
Node.appendChild
The last line of the code snippet is app?.appendChild(p) . The previous, document.getElementById ,
section detailed that the optional chaining operator is used here because app can potentially be null at run-
time. The appendChild method is defined by:
This method works similarly to the createElement method as the generic parameter T is inferred from
the newChild argument. T is constrained to another base interface Node .
809
typescript
<div>
<p>Hello, World</p>
<p>TypeScript!</p>
</div>;
div.children;
// HTMLCollection(2) [p, p]
div.childNodes;
// NodeList(2) [p, p]
After capturing the div element, the children prop will return a HTMLCollection list containing the
HTMLParagraphElements . The childNodes property will return a similar NodeList list of nodes. Each p
tag will still be of type HTMLParagraphElements , but the NodeList can contain additional HTML nodes
that the HTMLCollection list cannot.
Modify the html by removing one of the p tags, but keep the text.
<div>
<p>Hello, World</p>
TypeScript!
</div>;
div.children;
// HTMLCollection(1) [p]
div.childNodes;
// NodeList(2) [p, text]
See how both lists change. children now only contains the <p>Hello, World</p> element, and the
childNodes contains a text node rather than two p nodes. The text part of the NodeList is the liter-
al Node containing the text TypeScript! . The children list does not contain this Node because it is not
considered an HTMLElement .
/**
* Returns the first element that is a descendant of node that matches selectors.
*/
querySelector<K extends keyof HTMLElementTagNameMap>(selectors: K):
810
DOM Manipulation
HTMLElementTagNameMap[K] | null;
querySelector<K extends keyof SVGElementTagNameMap>(selectors: K):
SVGElementTagNameMap[K] | null;
querySelector<E extends Element = Element>(selectors: string): E | null;
/**
* Returns all element descendants of node that match selectors.
*/
querySelectorAll<K extends keyof HTMLElementTagNameMap>(selectors: K):
NodeListOf<HTMLElementTagNameMap[K]>;
querySelectorAll<K extends keyof SVGElementTagNameMap>(selectors: K):
NodeListOf<SVGElementTagNameMap[K]>;
querySelectorAll<E extends Element = Element>(selectors: string): NodeListOf<E>;
<ul>
<li>First :)</li>
<li>Second!</li>
<li>Third times a charm.</li>
</ul>;
Sources:
ECMA-262 Standard
Introduction to the DOM
Go to TOC
811
typescript
This quick start guide will teach you how to build TypeScript with gulp and then add Browserify, terser, or
Watchify to the gulp pipeline. This guide also shows how to add Babel functionality using Babelify.
Minimal project
Let's start out with a new directory. We'll name it proj for now, but you can change it to whatever you
want.
mkdir proj
cd proj
proj/
├─ src/
└─ dist/
TypeScript files will start out in your src folder, run through the TypeScript compiler and end up in dist .
mkdir src
mkdir dist
npm init
You'll be given a series of prompts. You can use the defaults except for your entry point. For your entry
point, use ./dist/main.js . You can always go back and change these in the package.json file that's
been generated for you.
Then install typescript , gulp and gulp-typescript in your project's dev dependencies. Gulp-type-
script is a gulp plugin for TypeScript.
812
Gulp
{
"files": ["src/main.ts"],
"compilerOptions": {
"noImplicitAny": true,
"target": "es5"
}
}
Create a gulpfile.js
In the project root, create the file gulpfile.js :
gulp.task("default", function () {
return tsProject.src().pipe(tsProject()).js.pipe(gulp.dest("dist"));
});
813
typescript
console.log(sayHello("TypeScript"));
{
"files": ["src/main.ts", "src/greet.ts"],
"compilerOptions": {
"noImplicitAny": true,
"target": "es5"
}
}
Make sure that the modules work by running gulp and then testing in Node:
gulp
node dist/main.js
Notice that even though we used ES2015 module syntax, TypeScript emitted CommonJS modules that Node
uses. We'll stick with CommonJS for this tutorial, but you could set module in the options object to change
this.
Browserify
Now let's move this project from Node to the browser. To do this, we'd like to bundle all our modules into
one JavaScript file. Fortunately, that's exactly what Browserify does. Even better, it lets us use the
CommonJS module system used by Node, which is the default TypeScript emit. That means our TypeScript
and Node setup will transfer to the browser basically unchanged.
First, install browserify, tsify, and vinyl-source-stream. tsify is a Browserify plugin that, like gulp-typescript,
gives access to the TypeScript compiler. vinyl-source-stream lets us adapt the file output of Browserify back
into a format that gulp understands called vinyl.
Create a page
Create a file in src named index.html :
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello World!</title>
</head>
<body>
<p id="greeting">Loading ...</p>
<script src="bundle.js"></script>
</body>
</html>
814
Gulp
showHello("greeting", "TypeScript");
Calling showHello calls sayHello to change the paragraph's text. Now change your gulpfile to the
following:
gulp.task("copy-html", function () {
return gulp.src(paths.pages).pipe(gulp.dest("dist"));
});
gulp.task(
"default",
gulp.series(gulp.parallel("copy-html"), function () {
return browserify({
basedir: ".",
debug: true,
entries: ["src/main.ts"],
cache: {},
packageCache: {},
})
.plugin(tsify)
.bundle()
.pipe(source("bundle.js"))
.pipe(gulp.dest("dist"));
})
);
This adds the copy-html task and adds it as a dependency of default . That means any time default is
run, copy-html has to run first. We've also changed default to call Browserify with the tsify plugin in-
stead of gulp-typescript. Conveniently, they both allow us to pass the same options object to the TypeScript
compiler.
After calling bundle we use source (our alias for vinyl-source-stream) to name our output bundle bun‐
dle.js .
Test the page by running gulp and then opening dist/index.html in a browser. You should see "Hello
from TypeScript" on the page.
815
typescript
Notice that we specified debug: true to Browserify. This causes tsify to emit source maps inside the bun-
dled JavaScript file. Source maps let you debug your original TypeScript code in the browser instead of the
bundled JavaScript. You can test that source maps are working by opening the debugger for your browser
and putting a breakpoint inside main.ts . When you refresh the page the breakpoint should pause the page
and let you debug greet.ts .
Watchify starts gulp and keeps it running, incrementally compiling whenever you save a file. This lets you
keep an edit-save-refresh cycle going in the browser.
Babel is a hugely flexible compiler that converts ES2015 and beyond into ES5 and ES3. This lets you add
extensive and customized transformations that TypeScript doesn't support.
Watchify
We'll start with Watchify to provide background compilation:
gulp.task("copy-html", function () {
return gulp.src(paths.pages).pipe(gulp.dest("dist"));
});
function bundle() {
return watchedBrowserify
.bundle()
816
Gulp
.on("error", fancy_log)
.pipe(source("bundle.js"))
.pipe(gulp.dest("dist"));
}
There are basically three changes here, but they require you to refactor your code a bit.
1. We wrapped our browserify instance in a call to watchify , and then held on to the result.
2. We called watchedBrowserify.on('update', bundle); so that Browserify will run the bundle func-
tion every time one of your TypeScript files changes.
3. We called watchedBrowserify.on('log', fancy_log); to log to the console.
Together (1) and (2) mean that we have to move our call to browserify out of the default task. And we
have to give the function for default a name since both Watchify and Gulp need to call it. Adding logging
with (3) is optional but very useful for debugging your setup.
Now when you run Gulp, it should start and stay running. Try changing the code for showHello in
main.ts and saving it. You should see output that looks like this:
proj$ gulp
[10:34:20] Using gulpfile ~/src/proj/gulpfile.js
[10:34:20] Starting 'copy-html'...
[10:34:20] Finished 'copy-html' after 26 ms
[10:34:20] Starting 'default'...
[10:34:21] 2824 bytes written (0.13 seconds)
[10:34:21] Finished 'default' after 1.36 s
[10:35:22] 2261 bytes written (0.02 seconds)
[10:35:24] 2808 bytes written (0.05 seconds)
Terser
First install Terser. Since the point of Terser is to mangle your code, we also need to install vinyl-buffer and
gulp-sourcemaps to keep sourcemaps working.
gulp.task("copy-html", function () {
return gulp.src(paths.pages).pipe(gulp.dest("dist"));
817
typescript
});
gulp.task(
"default",
gulp.series(gulp.parallel("copy-html"), function () {
return browserify({
basedir: ".",
debug: true,
entries: ["src/main.ts"],
cache: {},
packageCache: {},
})
.plugin(tsify)
.bundle()
.pipe(source("bundle.js"))
.pipe(buffer())
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(terser())
.pipe(sourcemaps.write("./"))
.pipe(gulp.dest("dist"));
})
);
Notice that terser itself has just one call — the calls to buffer and sourcemaps exist to make sure
sourcemaps keep working. These calls give us a separate sourcemap file instead of using inline sourcemaps
like before. Now you can run Gulp and check that bundle.js does get minified into an unreadable mess:
gulp
cat dist/bundle.js
Babel
First install Babelify and the Babel preset for ES2015. Like Terser, Babelify mangles code, so we'll need
vinyl-buffer and gulp-sourcemaps. By default Babelify will only process files with extensions of .js , .es ,
.es6 and .jsx so we need to add the .ts extension as an option to Babelify.
gulp.task("copy-html", function () {
return gulp.src(paths.pages).pipe(gulp.dest("dist"));
});
gulp.task(
"default",
818
Gulp
gulp.series(gulp.parallel("copy-html"), function () {
return browserify({
basedir: ".",
debug: true,
entries: ["src/main.ts"],
cache: {},
packageCache: {},
})
.plugin(tsify)
.transform("babelify", {
presets: ["es2015"],
extensions: [".ts"],
})
.bundle()
.pipe(source("bundle.js"))
.pipe(buffer())
.pipe(sourcemaps.init({ loadMaps: true }))
.pipe(sourcemaps.write("./"))
.pipe(gulp.dest("dist"));
})
);
We also need to have TypeScript target ES2015. Babel will then produce ES5 from the ES2015 code that
TypeScript emits. Let's modify tsconfig.json :
{
"files": ["src/main.ts"],
"compilerOptions": {
"noImplicitAny": true,
"target": "es2015"
}
}
Babel's ES5 output should be very similar to TypeScript's output for such a simple script.
Go to TOC
819
typescript
TypeScript doesn't exist in a vacuum. It was built with the JavaScript ecosystem in mind, and a lot of
JavaScript exists today. Converting a JavaScript codebase over to TypeScript is, while somewhat tedious,
usually not challenging. In this tutorial, we're going to look at how you might start out. We assume you've
read enough of the handbook to write new TypeScript code.
If you're looking to convert a React project, we recommend looking at the React Conversion Guide first.
If that's the case, the files that you've written are going to be used as inputs to TypeScript, and you'll run
the outputs it produces. During our JS to TS migration, we'll need to separate our input files to prevent
TypeScript from overwriting them. If your output files need to reside in a specific directory, then that will be
your output directory.
You might also be running some intermediate steps on your JavaScript, such as bundling or using another
transpiler like Babel. In this case, you might already have a folder structure like this set up.
From this point on, we're going to assume that your directory is set up something like this:
projectRoot
├── src
│ ├── file1.js
│ └── file2.js
├── built
└── tsconfig.json
If you have a tests folder outside of your src directory, you might have one tsconfig.json in src ,
and one in tests as well.
{
"compilerOptions": {
"outDir": "./built",
"allowJs": true,
"target": "es5"
},
"include": ["./src/**/*"]
}
820
Migrating from JavaScript
At this point, if you try running tsc at the root of your project, you should see output files in the built
directory. The layout of files in built should look identical to the layout of src . You should now have
TypeScript working with your project.
Early Benefits
Even at this point you can get some great benefits from TypeScript understanding your project. If you open
up an editor like VS Code or Visual Studio, you'll see that you can often get some tooling support like com-
pletion. You can also catch certain bugs with options like:
noImplicitReturns which prevents you from forgetting to return at the end of a function.
noFallthroughCasesInSwitch which is helpful if you never want to forget a break statement between
case s in a switch block.
TypeScript will also warn about unreachable code and labels, which you can disable with allowUnreach‐
ableCode and allowUnusedLabels respectively.
Gulp
If you're using Gulp in some fashion, we have a tutorial on using Gulp with TypeScript, and integrating with
common build tools like Browserify, Babelify, and Uglify. You can read more there.
Webpack
Webpack integration is pretty simple. You can use ts-loader , a TypeScript loader, combined with source-
map-loader for easier debugging. Simply run
and merge in options from the following into your webpack.config.js file:
module.exports = {
entry: "./src/index.ts",
output: {
filename: "./dist/bundle.js",
},
821
typescript
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"],
},
module: {
rules: [
// All files with a '.ts' or '.tsx' extension will be handled by 'ts-
loader'.
{ test: /\.tsx?$/, loader: "ts-loader" },
// All output '.js' files will have any sourcemaps re-processed by 'source-
map-loader'.
{ test: /\.js$/, loader: "source-map-loader" },
],
},
// Other options...
};
It's important to note that ts-loader will need to run before any other loader that deals with .js files.
The same goes for awesome-typescript-loader, another TypeScript loader for Webpack. You can read more
about the differences between the two here.
You can see an example of using Webpack in our tutorial on React and Webpack.
Finished with that step? Great! You've successfully migrated a file from JavaScript to TypeScript!
Of course, that might not feel right. If you open that file in an editor with TypeScript support (or if you run
tsc --pretty ), you might see red squiggles on certain lines. You should think of these the same way
you'd think of red squiggles in an editor like Microsoft Word. TypeScript will still translate your code, just like
Word will still let you print your documents.
If that sounds too lax for you, you can tighten that behavior up. If, for instance, you don't want TypeScript
to compile to JavaScript in the face of errors, you can use the noEmitOnError option. In that sense,
TypeScript has a dial on its strictness, and you can turn that knob up as high as you want.
If you plan on using the stricter settings that are available, it's best to turn them on now (see Getting
Stricter Checks below). For instance, if you never want TypeScript to silently infer any for a type without
you explicitly saying so, you can use noImplicitAny before you start modifying your files. While it might
feel somewhat overwhelming, the long-term gains become apparent much more quickly.
822
Migrating from JavaScript
// For Node/CommonJS
declare function require(path: string): any;
or
// For RequireJS/AMD
declare function define(...args: any[]): any;
it's better to get rid of those calls and use TypeScript syntax for imports.
First, you'll need to enable some module system by setting TypeScript's module option. Valid options are
commonjs , amd , system , and umd .
foo.doStuff();
foo.doStuff();
823
typescript
If you're using a module option other than commonjs , you'll need to set your moduleResolution option to
node .
After that, you'll be able to import lodash with no issues, and get accurate completions.
Sometimes you'll entirely overwrite the exports object. This is a common pattern people use to make their
modules immediately callable like in this snippet:
function foo() {
// ...
}
module.exports = foo;
function foo() {
// ...
}
export = foo;
function myCoolFunction() {
if (arguments.length == 2 && !Array.isArray(arguments[1])) {
var f = arguments[0];
var arr = arguments[1];
// ...
}
// ...
824
Migrating from JavaScript
myCoolFunction(
function (x) {
console.log(x);
},
[1, 2, 3, 4]
);
myCoolFunction(
function (x) {
console.log(x);
},
1,
2,
3,
4
);
In this case, we need to use TypeScript to tell any of our callers about the ways myCoolFunction can be
called using function overloads.
We added two overload signatures to myCoolFunction . The first checks states that myCoolFunction
takes a function (which takes a number ), and then a list of number s. The second one says that it will take
a function as well, and then uses a rest parameter ( ...nums ) to state that any number of arguments after
that need to be number s.
TypeScript will say that you can't assign to color and volume because it first figured out the type of op‐
tions as {} which doesn't have any properties. If you instead moved the declarations into the object liter-
al themselves, you'd get no errors:
let options = {
color: "red",
volume: 11,
};
825
typescript
You could also define the type of options and add a type assertion on the object literal.
interface Options {
color: string;
volume: number;
}
Alternatively, you can just say options has the type any which is the easiest thing to do, but which will
benefit you the least.
For instance, if you have something that's typed as Object you won't be able to call methods like toLow‐
erCase() on it. Being more general usually means you can do less with a type, but any is special in that it
is the most general type while still allowing you to do anything with it. That means you can call it, construct
it, access properties on it, etc. Keep in mind though, whenever you use any , you lose out on most of the
error checking and editor support that TypeScript gives you.
If a decision ever comes down to Object and {} , you should prefer {} . While they are mostly the same,
technically {} is a more general type than Object in certain esoteric cases.
No Implicit any
There are certain cases where TypeScript can't figure out what certain types should be. To be as lenient as
possible, it will decide to use the type any in its place. While this is great for migration, using any means
that you're not getting any type safety, and you won't get the same tooling support you'd get elsewhere.
You can tell TypeScript to flag these locations down and give an error with the noImplicitAny option.
826
Migrating from JavaScript
When strictNullChecks is enabled, null and undefined get their own types called null and unde‐
fined respectively. Whenever anything is possibly null , you can use a union type with the original type.
So for instance, if something could be a number or null , you'd write the type out as number | null .
If you ever have a value that TypeScript thinks is possibly null / undefined , but you know better, you can
use the postfix ! operator to tell it otherwise.
As a heads up, when using strictNullChecks , your dependencies may need to be updated to use
strictNullChecks as well.
class Point {
constructor(public x, public y) {}
getDistance(p: Point) {
let dx = p.x - this.x;
let dy = p.y - this.y;
return Math.sqrt(dx ** 2 + dy ** 2);
}
}
// ...
This has the same problems we mentioned above - we could easily have misspelled getDistance and not
gotten an error. For this reason, TypeScript has the noImplicitThis option. When that option is set,
TypeScript will issue an error when this is used without an explicit (or inferred) type. The fix is to use a
this -parameter to give an explicit type in the interface or in the function itself:
Go to TOC
827
typescript
TypeScript supports JSX and can correctly model the patterns used in React codebases like useState .
All of these are great starting points. We use Gatsby with TypeScript for this website, so that can also be a
useful reference implementation.
Documentation
Here are some of the best places to find up-to-date information on React and TypeScript:
Go to TOC
828
TypeScript Tooling in 5 minutes
Installing TypeScript
There are two main ways to get the TypeScript available for your project:
Visual Studio 2017 and Visual Studio 2015 Update 3 include TypeScript language support by default but
does not include the TypeScript compiler, tsc . If you didn't install TypeScript with Visual Studio, you can
still download it.
// @noImplicitAny: false
function greeter(person) {
return "Hello, " + person;
}
document.body.textContent = greeter(user);
tsc greeter.ts
The result will be a file greeter.js which contains the same JavaScript that you fed in. We're up and run-
ning using TypeScript in our JavaScript app!
Now we can start taking advantage of some of the new tools TypeScript offers. Add a : string type anno-
tation to the 'person' function argument as shown here:
829
typescript
document.body.textContent = greeter(user);
Type annotations
Type annotations in TypeScript are lightweight ways to record the intended contract of the function or vari-
able. In this case, we intend the greeter function to be called with a single string parameter. We can try
changing the call greeter to pass an array instead:
// @errors: 2345
function greeter(person: string) {
return "Hello, " + person;
}
document.body.textContent = greeter(user);
Similarly, try removing all the arguments to the greeter call. TypeScript will let you know that you have
called this function with an unexpected number of parameters. In both cases, TypeScript can offer static
analysis based on both the structure of your code, and the type annotations you provide.
Notice that although there were errors, the greeter.js file is still created. You can use TypeScript even if
there are errors in your code. But in this case, TypeScript is warning that your code will likely not run as
expected.
Interfaces
Let's develop our sample further. Here we use an interface that describes objects that have a firstName and
lastName field. In TypeScript, two types are compatible if their internal structure is compatible. This allows
us to implement an interface just by having the shape the interface requires, without an explicit imple‐
ments clause.
interface Person {
firstName: string;
lastName: string;
}
document.body.textContent = greeter(user);
830
TypeScript Tooling in 5 minutes
Classes
Finally, let's extend the example one last time with classes. TypeScript supports new features in JavaScript,
like support for class-based object-oriented programming.
Here we're going to create a Student class with a constructor and a few public fields. Notice that classes
and interfaces play well together, letting the programmer decide on the right level of abstraction.
Also of note, the use of public on arguments to the constructor is a shorthand that allows us to automati-
cally create properties with that name.
class Student {
fullName: string;
constructor(
public firstName: string,
public middleInitial: string,
public lastName: string
) {
this.fullName = firstName + " " + middleInitial + " " + lastName;
}
}
interface Person {
firstName: string;
lastName: string;
}
document.body.textContent = greeter(user);
Re-run tsc greeter.ts and you'll see the generated JavaScript is the same as the earlier code. Classes in
TypeScript are just a shorthand for the same prototype-based OO that is frequently used in JavaScript.
<!DOCTYPE html>
<html>
<head>
<title>TypeScript Greeter</title>
</head>
<body>
<script src="greeter.js"></script>
</body>
</html>
Open greeter.html in the browser to run your first simple TypeScript web application!
831
typescript
Optional: Open greeter.ts in Visual Studio, or copy the code into the TypeScript playground. You can
hover over identifiers to see their types. Notice that in some cases these types are inferred automatically for
you. Re-type the last line, and see completion lists and parameter help based on the types of the DOM ele-
ments. Put your cursor on the reference to the greeter function, and hit F12 to go to its definition. Notice,
too, that you can right-click on a symbol and use refactoring to rename it.
The type information provided works together with the tools to work with JavaScript at application scale. For
more examples of what's possible in TypeScript, see the Samples section of the website.
Go to TOC
832
Colophon
Colophon
This book is created by using the following sources:
Typescript - English
GitHub source: microsoft/TypeScript-Website/packages/documentation/copy/en
Created: 2022-12-11
Bash v5.2.2
Vivliostyle, https://vivliostyle.org/
By: @shinokada
Viewer: https://read-html-download-pdf.vercel.app/
GitHub repo: https://github.com/shinokada/markdown-docs-as-pdf
Viewer repo: https://github.com/shinokada/read-html-download-pdf
833