04 TypeScriptHandbook 3.7
04 TypeScriptHandbook 3.7
Introduction 1.1
Tutorials 1.2
Angular 1.2.1
TypeScript-3.7 1.3.2
TypeScript-3.6 1.3.3
TypeScript-3.5 1.3.4
TypeScript-3.4 1.3.5
TypeScript-3.3 1.3.6
TypeScript-3.2 1.3.7
TypeScript-3.1 1.3.8
TypeScript-3.0 1.3.9
TypeScript-2.9 1.3.10
TypeScript-2.8 1.3.11
TypeScript-2.7 1.3.12
TypeScript-2.6 1.3.13
TypeScript-2.5 1.3.14
TypeScript-2.4 1.3.15
TypeScript-2.3 1.3.16
TypeScript-2.2 1.3.17
TypeScript-2.1 1.3.18
TypeScript-2.0 1.3.19
TypeScript-1.8 1.3.20
TypeScript-1.7 1.3.21
TypeScript-1.6 1.3.22
TypeScript-1.5 1.3.23
TypeScript-1.4 1.3.24
TypeScript-1.3 1.3.25
TypeScript-1.1 1.3.26
Handbook 1.4
1
Basic Types
1.4.1
Variable Declarations 1.4.2
Interfaces 1.4.3
Classes 1.4.4
Functions 1.4.5
Generics 1.4.6
Enums 1.4.7
Symbols 1.4.11
JSX 1.4.18
Decorators 1.4.19
Mixins 1.4.20
By Example 1.5.3
Templates 1.5.6
Consumption 1.5.8
2
Introduction
TypeScript-Handbook (3.7)
The PDF is generated by gitbook. The document is based on Typescript handlebook
For a more formal description of the language, see the latest TypeScript Language Specification.
3
Tutorials
Tutorials
Angular
ASP.Net Core
Gulp
Migratng from JavaScript
React & Webpack
React
Typescript in 5 minutes
4
Angular
Angluar
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!
5
ASP.NET Core
Setup
Install ASP.NET Core and TypeScript
First, install ASP.NET Core if you need it. This quick-start guide requires Visual Studio 2015 or 2017.
Next, if your version of Visual Studio does not already have the latest TypeScript, you can install it.
1. Name your project and solution. After select the Create button
6
ASP.NET Core
1. In the last window, select the Empty template and press the Create button
7
ASP.NET Core
Open up your Startup.cs file and edit your Configure function to look like this:
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 .
8
ASP.NET Core
9
ASP.NET Core
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
10
ASP.NET Core
{
"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 .
11
ASP.NET Core
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.
12
ASP.NET Core
Set up gulp #
Right click on the project and click New Item. Then choose JavaScript File and use the name of gulpfile.js
var paths = {
scripts: ['scripts/**/*.js', 'scripts/**/*.ts', 'scripts/**/*.map'],
};
gulp.task('clean', function () {
13
ASP.NET Core
return del(['wwwroot/scripts/**/*']);
});
gulp.task('default', function () {
gulp.src(paths.scripts).pipe(gulp.dest('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.
If ‘default’ and ‘clean’ tasks don’t show up, refresh the explorer:
14
ASP.NET Core
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<script src="scripts/app.js"></script>
<title></title>
</head>
<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.
15
ASP.NET Core
Congrats you've built your own .NET Core project with a TypeScript frontend.
16
Gulp
This quick start guide will teach you how to build TypeScript with gulp and then add Browserify, uglify, 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-typescript is a gulp plugin for
Typescript.
17
Gulp
{
"files": [
"src/main.ts"
],
"compilerOptions": {
"noImplicitAny": true,
"target": "es5"
}
}
Create a gulpfile.js
gulp.task('default', function () {
return tsProject.src()
.pipe(tsProject())
.js.pipe(gulp.dest('dist'));
});
18
Gulp
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>
19
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'));
});
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 instead 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 bundle.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.
Notice that we specified debug: true to Browserify. This causes tsify to emit source maps inside the bundled 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.
20
Gulp
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()
.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 function 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
21
Gulp
Uglify
First install Uglify. Since the point of Uglify 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'));
});
Notice that uglify 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
22
Gulp
Babel
First install Babelify and the Babel preset for ES2015. Like Uglify, 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'));
});
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.
23
Migrating from JavaScript
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/**/*"
]
}
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.
24
Migrating from JavaScript
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 completion. 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 allowUnreachableCode 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 awesome-typescript-loader , a TypeScript loader, combined with source-
and merge in options from the following into your webpack.config.js file:
module.exports = {
entry: "./src/index.ts",
output: {
filename: "./dist/bundle.js",
},
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"]
},
module: {
loaders: [
// All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'.
{ test: /\.tsx?$/, loader: "awesome-typescript-loader" }
],
preLoaders: [
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ test: /\.js$/, loader: "source-map-loader" }
25
Migrating from JavaScript
]
},
// Other options...
};
It's important to note that awesome-typescript-loader will need to run before any other loader that deals with .js files.
The same goes for ts-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.
// 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.
26
Migrating from JavaScript
First, you'll need to enable some module system by setting TypeScript's module flag. Valid options are commonjs , amd ,
system , and umd .
foo.doStuff();
define(["foo"], function(foo) {
foo.doStuff();
})
foo.doStuff();
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.
module.exports.feedPets = function(pets) {
// ...
}
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:
27
Migrating from JavaScript
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];
// ...
}
// ...
}
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.
28
Migrating from JavaScript
TypeScript will say that you can't assign to color and volume because it first figured out the type of options as {} which
doesn't have any properties. If you instead moved the declarations into the object literal themselves, you'd get no errors:
let options = {
color: "red",
volume: 11
};
You could also define the type of options and add a type assertion on the object literal.
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 toLowerCase() 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.
When strictNullChecks is enabled, null and undefined get their own types called null and undefined 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 .
29
Migrating from JavaScript
If you ever have a value that TypeScript thinks is possibly null / undefined , but you know better, you can use the postfix !
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:
30
React & Webpack
This guide will teach you how to wire up TypeScript with React and webpack.
If you're starting a brand new project, take a look at the React Quick Start guide first.
mkdir proj
cd proj
proj/
├─ dist/
└─ src/
└─ components/
TypeScript files will start out in your src folder, run through the TypeScript compiler, then webpack, and end up in a main.js
file in dist . Any components that we write will go in the src/components folder.
mkdir src
cd src
mkdir components
cd ..
npm init -y
Webpack is a tool that will bundle your code and optionally all of its dependencies into a single .js file.
Let's now add React and React-DOM, along with their declaration files, as dependencies to your package.json file:
31
React & Webpack
That @types/ prefix means that we also want to get the declaration files for React and React-DOM. Usually when you import a
path like "react" , it will look inside of the react package itself; however, not all packages include declaration files, so
TypeScript also looks in the @types/react package as well. You'll see that we won't even have to think about this later on.
Both of these dependencies will let TypeScript and webpack play well together. ts-loader helps Webpack compile your
TypeScript code using the TypeScript's standard configuration file named tsconfig.json . source-map-loader uses any
sourcemap outputs from TypeScript to inform webpack when generating its own sourcemaps. This will allow you to debug your
final output file as if you were debugging your original TypeScript source code.
Please note that ts-loader is not the only loader for typescript.
Notice that we installed TypeScript as a development dependency. We could also have linked TypeScript to a global copy with
npm link typescript , but this is a less common scenario.
To do this, you'll need to create a tsconfig.json which contains a list of your input files as well as all your compilation settings.
Simply create a new file in your project root named tsconfig.json and fill it with the following contents:
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es6",
"jsx": "react"
}
}
export const Hello = (props: HelloProps) => <h1>Hello from {props.compiler} and {props.framework}!</h1>;
Note that while this example uses function components, we could also make our example a little classier as well.
32
React & Webpack
ReactDOM.render(
<Hello compiler="TypeScript" framework="React" />,
document.getElementById("example")
);
We just imported our Hello component into index.tsx . Notice that unlike with "react" or "react-dom" , we used a
relative path to Hello.tsx - this is important. If we hadn't, TypeScript would've instead tried looking in our node_modules
folder.
We'll also need a page to display our Hello component. Create a file at the root of proj named index.html with the
following contents:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>Hello React!</title>
</head>
<body>
<div id="example"></div>
Notice that we're including files from within node_modules . React and React-DOM's npm packages include standalone .js
files that you can include in a web page, and we're referencing them directly to get things moving faster. Feel free to copy these
files to another directory, or alternatively, host them on a content delivery network (CDN). Facebook makes CDN-hosted versions
of React available, and you can read more about that here.
module.exports = {
mode: "production",
33
React & Webpack
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: [".ts", ".tsx"]
},
module: {
rules: [
{
test: /\.ts(x?)$/,
exclude: /node_modules/,
use: [
{
loader: "ts-loader"
}
]
},
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{
enforce: "pre",
test: /\.js$/,
loader: "source-map-loader"
}
]
},
// When importing a module whose path matches one of the following, just
// assume a corresponding global variable exists and use that instead.
// This is important because it allows us to avoid bundling all of our
// dependencies, which allows browsers to cache those libraries between builds.
externals: {
"react": "React",
"react-dom": "ReactDOM"
}
};
You might be wondering about that externals field. We want to avoid bundling all of React into the same file, since this
increases compilation time and browsers will typically be able to cache a library if it doesn't change.
Ideally, we'd just import the React module from within the browser, but most browsers still don't quite support modules yet.
Instead libraries have traditionally made themselves available using a single global variable like jQuery or _ . This is called the
"namespace pattern", and webpack allows us to continue leveraging libraries written that way. With our entry for "react":
"React" , webpack will work its magic to make any import of "react" load from the React variable.
npx webpack
Now open up index.html in your favorite browser and everything should be ready to use! You should see a page that says
"Hello from TypeScript and React!"
34
React
This quick start guide will teach you how to wire up TypeScript with React. By the end, you'll have
We assume that you're already using Node.js with npm. You may also want to get a sense of the basics with React.
Install create-react-app
We're going to use the create-react-app because it sets some useful tools and canonical defaults for React projects. This is just a
command-line utility to scaffold out new React projects.
react-scripts-ts is a set of adjustments to take the standard create-react-app project pipeline and bring TypeScript into the mix.
At this point, your project layout should look like the following:
my-app/
├─ .gitignore
├─ node_modules/
├─ public/
├─ src/
│ └─ ...
├─ package.json
├─ tsconfig.json
└─ tslint.json
Of note:
35
React
This runs the start script specified in our package.json , and will spawn off a server which reloads the page as we save our
files. Typically the server runs at http://localhost:3000 , but should be automatically opened for you.
This command runs Jest, an incredibly useful testing utility, against all files whose extensions end in .test.ts or .spec.ts .
Like with the npm run start command, Jest will automatically run as soon as it detects changes. If you'd like, you can run npm
run start and npm run test side by side so that you can preview changes and test them simultaneously.
This will create an optimized JS and CSS build in ./build/static/js and ./build/static/css respectively.
You won't need to run a production build most of the time, but it is useful if you need to measure things like the final size of your
app.
Creating a component
We're going to write a Hello component. The component will take the name of whatever we want to greet (which we'll call
name ), and optionally the number of exclamation marks to trail with ( enthusiasmLevel ).
When we write something like <Hello name="Daniel" enthusiasmLevel={3} /> , the component should render to something like
<div>Hello Daniel!!!</div> . If enthusiasmLevel isn't specified, the component should default to showing one exclamation
mark. If enthusiasmLevel is 0 or negative, it should throw an error.
// src/components/Hello.tsx
36
React
return (
<div className="hello">
<div className="greeting">
Hello {name + getExclamationMarks(enthusiasmLevel)}
</div>
</div>
);
}
// helpers
Notice that we defined a type named Props that specifies the properties our component will take. name is a required string ,
and enthusiasmLevel is an optional number (which you can tell from the ? that we wrote out after its name).
We also wrote Hello as a function component. To be specific, Hello is a function that takes a Props object, and destructures
it. If enthusiasmLevel isn't given in our Props object, it will default to 1 .
Writing functions is one of two primary ways React allows us to make components). If we wanted, we could have written it out as
a class as follows:
if (enthusiasmLevel <= 0) {
throw new Error('You could be a little more enthusiastic. :D');
}
return (
<div className="hello">
<div className="greeting">
Hello {name + getExclamationMarks(enthusiasmLevel)}
</div>
</div>
);
}
}
Classes are useful when our component instances have some state. But we don't really need to think about state in this example -
in fact, we specified it as object in React.Component<Props, object> , so writing a function component tends to be shorter.
Local component state is more useful at the presentational level when creating generic UI elements that can be shared between
libraries. For our application's lifecycle, we will revisit how applications manage general state with Redux in a bit.
Now that we've written our component, let's dive into index.tsx and replace our render of <App /> with a render of <Hello
... /> .
37
React
ReactDOM.render(
<Hello name="TypeScript" enthusiasmLevel={10} />,
document.getElementById('root') as HTMLElement
);
Type assertions
One final thing we'll point out in this section is the line document.getElementById('root') as HTMLElement . This syntax is
called a type assertion, sometimes also called a cast. This is a useful way of telling TypeScript what the real type of an expression
is when you know better than the type checker.
The reason we need to do so in this case is that getElementById 's return type is HTMLElement | null . Put simply,
getElementById returns null when it can't find an element with a given id . We're assuming that getElementById will
actually succeed, so we need convince TypeScript of that using the as syntax.
TypeScript also has a trailing "bang" syntax ( ! ), which removes null and undefined from the prior expression. So we could
have written document.getElementById('root')! , but in this case we wanted to be a bit more explicit.
Adding style
Styling a component with our setup is easy. To style our Hello component, we can create a CSS file at
src/components/Hello.css .
.hello {
text-align: center;
margin: 20px;
font-size: 48px;
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif
}
.hello button {
margin-left: 25px;
margin-right: 25px;
font-size: 40px;
min-width: 50px;
}
The tools that create-react-app uses (namely, Webpack and various loaders) allow us to just import the stylesheets we're interested
in. When our build runs, any imported .css files will be concatenated into an output file. So in src/components/Hello.tsx ,
we'll add the following import.
import './Hello.css';
When we write something like <Hello name="Daniel" enthusiasmLevel={3} /> , the component should render to
something like <div>Hello Daniel!!!</div> .
If enthusiasmLevel isn't specified, the component should default to showing one exclamation mark.
If enthusiasmLevel is 0 or negative, it should throw an error.
We can use these requirements to write a few tests for our components.
38
React
But first, let's install Enzyme. Enzyme is a common tool in the React ecosystem that makes it easier to write tests for how
components will behave. By default, our application includes a library called jsdom to allow us to simulate the DOM and test its
runtime behavior without a browser. Enzyme is similar, but builds on jsdom and makes it easier to make certain queries about our
components.
Notice we installed packages enzyme as well as @types/enzyme . The enzyme package refers to the package containing
JavaScript code that actually gets run, while @types/enzyme is a package that contains declaration files ( .d.ts files) so that
TypeScript can understand how you can use Enzyme. You can learn more about @types packages here.
Now that we've got Enzyme set up, let's start writing our test! Let's create a file named src/components/Hello.test.tsx ,
adjacent to our Hello.tsx file from earlier.
// src/components/Hello.test.tsx
it('renders the correct text with an explicit enthusiasm level of 5', () => {
const hello = enzyme.shallow(<Hello name='Daniel' enthusiasmLevel={5} />);
expect(hello.find(".greeting").text()).toEqual('Hello Daniel!!!!!');
});
These tests are extremely basic, but you should be able to get the gist of things.
39
React
At this point, if all you're using React for is fetching data once and displaying it, you can consider yourself done. But if you're
developing an app that's more interactive, then you may need to add state management.
Because React on its own does not provide built-in support for state management, the React community uses libraries like Redux
and MobX.
Redux relies on synchronizing data through a centralized and immutable store of data, and updates to that data will trigger a re-
render of our application. State is updated in an immutable fashion by sending explicit action messages which must be handled by
functions called reducers. Because of the explicit nature, it is often easier to reason about how an action will affect the state of
your program.
MobX relies on functional reactive patterns where state is wrapped through observables and passed through as props. Keeping
state fully synchronized for any observers is done by simply marking state as observable. As a nice bonus, the library is already
written in TypeScript.
There are various merits and tradeoffs to both. Generally Redux tends to see more widespread usage, so for the purposes of this
tutorial, we'll focus on adding Redux; however, you should feel encouraged to explore both.
The following section may have a steep learning curve. We strongly suggest you familiarize yourself with Redux through its
documentation.
For our purposes, we're going to add two buttons to control the enthusiasm level for our Hello component.
Installing Redux
To add Redux, we'll first install redux and react-redux , as well as their types, as a dependency.
In this case we didn't need to install @types/redux because Redux already comes with its own definition files ( .d.ts files).
which will contain definitions for types that we might use throughout the program.
// src/types/index.tsx
40
React
Our intention is that languageName will be the programming language this app was written in (i.e. TypeScript or JavaScript) and
enthusiasmLevel will vary. When we write our first container, we'll understand why we intentionally made our state slightly
different from our props.
Adding actions
Let's start off by creating a set of message types that our app can respond to in src/constants/index.tsx .
// src/constants/index.tsx
This const / type pattern allows us to use TypeScript's string literal types in an easily accessible and refactorable way.
Next, we'll create a set of actions and functions that can create these actions in src/actions/index.tsx .
We've created two types that describe what increment actions and decrement actions should look like. We also created a type
( EnthusiasmAction ) to describe cases where an action could be an increment or a decrement. Finally, we made two functions
that actually manufacture the actions which we can use instead of writing out bulky object literals.
There's clearly boilerplate here, so you should feel free to look into libraries like redux-actions once you've got the hang of things.
Adding a reducer
We're ready to write our first reducer! Reducers are just functions that generate changes by creating modified copies of our
application's state, but that have no side effects. In other words, they're what we call pure functions.
41
React
Our reducer will go under src/reducers/index.tsx . Its function will be to ensure that increments raise the enthusiasm level by
1, and that decrements reduce the enthusiasm level by 1, but that the level never falls below 1.
// src/reducers/index.tsx
Notice that we're using the object spread ( ...state ) which allows us to create a shallow copy of our state, while replacing the
enthusiasmLevel . It's important that the enthusiasmLevel property come last, since otherwise it would be overridden by the
property in our old state.
You may want to write a few tests for your reducer. Since reducers are pure functions, they can be passed arbitrary data. For every
input, reducers can tested by checking their newly produced state. Consider looking into Jest's toEqual method to accomplish this.
Making a container
When writing with Redux, we will often write components as well as containers. Components are often data-agnostic, and work
mostly at a presentational level. Containers typically wrap components and feed them any data that is necessary to display and
modify state. You can read more about this concept on Dan Abramov's article Presentational and Container Components.
First let's update src/components/Hello.tsx so that it can modify state. We'll add two optional callback properties to Props
Then we'll bind those callbacks to two new buttons that we'll add into our component.
return (
<div className="hello">
<div className="greeting">
Hello {name + getExclamationMarks(enthusiasmLevel)}
</div>
<div>
<button onClick={onDecrement}>-</button>
<button onClick={onIncrement}>+</button>
</div>
</div>
);
42
React
In general, it'd be a good idea to write a few tests for onIncrement and onDecrement being triggered when their respective
buttons are clicked. Give it a shot to get the hang of writing tests for your components.
Now that our component is updated, we're ready to wrap it into a container. Let's create a file named src/containers/Hello.tsx
The real two key pieces here are the original Hello component as well as the connect function from react-redux. connect
will be able to actually take our original Hello component and turn it into a container using two functions:
mapStateToProps which massages the data from the current store to part of the shape that our component needs.
mapDispatchToProps which uses creates callback props to pump actions to our store using a given dispatch function.
If we recall, our application state consists of two properties: languageName and enthusiasmLevel . Our Hello component, on
the other hand, expected a name and an enthusiasmLevel . mapStateToProps will get the relevant data from the store, and
adjust it if necessary, for our component's props. Let's go ahead and write that.
Note that mapStateToProps only creates 2 out of 4 of the properties a Hello component expects. Namely, we still want to pass
in the onIncrement and onDecrement callbacks. mapDispatchToProps is a function that takes a dispatcher function. This
dispatcher function can pass actions into our store to make updates, so we can create a pair of callbacks that will call the
dispatcher as necessary.
Finally, we're ready to call connect . connect will first take mapStateToProps and mapDispatchToProps , and then return
another function that we can use to wrap our component. Our resulting container is defined with the following line of code:
// src/containers/Hello.tsx
43
React
enthusiasmLevel,
name: languageName,
}
}
Creating a store
Let's go back to src/index.tsx . To put this all together, we need to create a store with an initial state, and set it up with all of
our reducers.
store is, as you might've guessed, our central store for our application's global state.
Next, we're going to swap our use of ./src/components/Hello with ./src/containers/Hello and use react-redux's Provider
ReactDOM.render(
<Provider store={store}>
<Hello />
</Provider>,
document.getElementById('root') as HTMLElement
);
Notice that Hello no longer needs props, since we used our connect function to adapt our application's state for our wrapped
Hello component's props.
Ejecting
If at any point, you feel like there are certain customizations that the create-react-app setup has made difficult, you can always
opt-out and get the various configuration options you need. For example, if you'd like to add a Webpack plugin, it might be
necessary to take advantage of the "eject" functionality that create-react-app provides.
Simply run
44
React
As a heads up, you may want to commit all your work before running an eject. You cannot undo an eject command, so opting out
is permanent unless you can recover from a commit prior to running an eject.
Next steps
create-react-app comes with a lot of great stuff. Much of it is documented in the default README.md that was generated for our
project, so give that a quick read.
If you still want to learn more about Redux, you can check out the official website for documentation. The same goes for MobX.
If you want to eject at some point, you may need to know a little bit more about Webpack. You can check out our React &
Webpack walkthrough here.
At some point you might need routing. There are several solutions, but react-router is probably the most popular for Redux
projects, and is often used in conjunction with react-router-redux.
45
TypeScript-in 5 minutes
Installing TypeScript
There are two main ways to get the TypeScript tools:
Visual Studio 2017 and Visual Studio 2015 Update 3 include TypeScript by default. If you didn't install TypeScript with Visual
Studio, you can still download it.
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 running using TypeScript
in our JavaScript app!
Now we can start taking advantage of some of the new tools TypeScript offers. Add a : string type annotation to the 'person'
function argument as shown here:
document.body.textContent = greeter(user);
Type annotations
46
TypeScript-in 5 minutes
Type annotations in TypeScript are lightweight ways to record the intended contract of the function or variable. 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:
document.body.textContent = greeter(user);
error TS2345: Argument of type 'number[]' is not assignable to parameter of type 'string'.
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 implements clause.
interface Person {
firstName: string;
lastName: string;
}
document.body.textContent = greeter(user);
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 automatically create properties
with that name.
class Student {
fullName: string;
47
TypeScript-in 5 minutes
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!
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 elements. 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.
48
What's New
Release Note
Overview
TypeScript 3.7
TypeScript 3.6
TypeScript 3.5
TypeScript 3.4
TypeScript 3.3
TypeScript 3.2
TypeScript 3.1
TypeScript 3.0
TypeScript 2.9
TypeScript 2.8
TypeScript 2.7
TypeScript 2.6
TypeScript 2.5
TypeScript 2.4
TypeScript 2.3
TypeScript 2.2
TypeScript 2.1
TypeScript 2.0
TypeScript 1.8
TypeScript 1.7
TypeScript 1.6
TypeScript 1.5
TypeScript 1.4
TypeScript 1.3
TypeScript 1.1
49
Overview
This page is all of the release notes combined into a single page. It's long, but comprehensive.
TypeScript 3.7
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 expressions if we run into a
null or undefined . The star of the show in optional chaining is the new ?. operator 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];
// equivalent to
50
Overview
There's also optional call, which allows us to conditionally call expressions if they're not null or undefined .
return result;
}
The "short-circuiting" behavior that optional chains have is limited property accesses, calls, element accesses - 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
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 optional 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
51
Overview
this is a new way to say that the value foo will be used when it's "present"; but when it's null or undefined , calculate
bar() in its place.
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 implementing 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.
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.
52
Overview
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 assertion 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 specific variable or property
has a different type.
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'?
53
Overview
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.
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.
54
Overview
return doThingWithNumber(x);
}
process.exit(1);
}
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 following causes an error.
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 resolving type arguments to
permit these patterns.
This means that code like the following that was trying to represent JSON...
type Json =
| string
| number
| boolean
| null
| JsonObject
| JsonArray;
interface JsonObject {
[property: string]: Json;
55
Overview
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.
type VirtualNode =
| string
| [string, { [key: string]: any }, ...VirtualNode[]];
For more information, you can read up on the original pull request.
The --declaration flag in TypeScript allows us to generate .d.ts files (declaration files) from TypeScript source files (i.e.
.ts and .tsx files). These .d.ts files are important for a couple of reasons.
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 underappreciated: 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 --declaration 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 understand 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 figures 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;
/**
56
Overview
// TODO
return result;
}
/**
* Produces a blurred image from an input buffer.
*
* @param input {Uint8Array}
* @param width {number}
* @param height {number}
*/
export function blurImage(input: Uint8Array, width: number, height: number): Uint8Array;
This can go beyond basic functions with @param tags too, where the following example:
/**
* @callback Job
* @returns {void}
*/
57
Overview
/**
* @callback Job
* @returns {void}
*/
/** Queues work */
export class Worker {
constructor(maxDepth?: number);
started: boolean;
depthLimit: number;
/**
* 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,
58
Overview
configurable: true,
writable: true,
value: 100
});
Object.defineProperty(this, "bar", {
enumerable: true,
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 incrementally to help users
mitigate potential future breakage. We've provided a new flag called useDefineForClassFields 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.
class AnimalHouse {
resident: Animal;
constructor(animal: Animal) {
this.resident = animal;
}
}
constructor(dog: Dog) {
super(dog);
}
}
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.
59
Overview
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 indicate that a property
should have no emit.
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 seperate 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.
We strongly encourage users to try the useDefineForClassFields flag and report back on our issue tracker or in the comments
below. This includes feedback on difficulty of adopting the flag so we can understand how we can make migration easier.
60
Overview
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 experience 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.
A common and dangerous error is to forget to invoke a function, especially if the function has zero arguments or is named in a
way that implies it might be a property rather than a function.
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!
This check is a breaking change, but for that reason the checks are very conservative. This error is only issued 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;
}
61
Overview
user.notify();
}
}
If you intended to test the function without calling it, you can correct the definition of it to include undefined / null , or use
!! to write something like if (!!user.isAdministrator) to indicate that the coercion is intentional.
We owe a big thanks to GitHub user @jwbay who took the initiative to create a proof-of-concept and iterated to provide us with
with the current version.
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.
62
Overview
While not a breakage per se, opting in to the useDefineForClassFields flag can cause breakage when:
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 conflict is unintentional and the best fix is to
rename the imported type. If the intent was to augment the imported type, a proper module augmentation should be written
instead.
63
Overview
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.
64
Overview
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!";
}
// prints:
65
Overview
//
// 0
// 1
// 2
// 3
// 4
// 5
// DONE!
For more details on the change, see the pull request here.
-downlevelIteration flag. The looser default without --downlevelIteration works fairly well; however, there were some
common cases where the transformation of array spreads had observable differences. For example, the following array containing
a spread
[...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;
location: string;
}
66
Overview
// ~~~~~~~~~~~~~
// Argument of type 'Promise<User>' is not assignable to parameter of type 'User'.
// ...
// Did you forget to use 'await'?
}
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)
As a result, users can write getters and setters in ambient contexts in TypeScript 3.6.
67
Overview
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.
TypeScript 3.0 introduced support for referencing other and building them incrementally using the --build flag. Additionally,
TypeScript 3.4 introduced the --incremental flag for saving information about previous compilations to only rebuild certain
files. These flags were incredibly useful for structuring projects more flexibly and speeding builds up. Unfortunately, using these
flags didn't work with 3rd party build tools like Gulp and Webpack. TypeScript 3.6 now exposes two sets of APIs to operate on
project references and incremental program building.
For creating --incremental builds, users can leverage the createIncrementalProgram and createIncrementalCompilerHost
APIs. Users can also re-hydrate old program instances from .tsbuildinfo files generated 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).
68
Overview
For leveraging project references, a new createSolutionBuilder function has been exposed, which returns 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.
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!
TypeScript 3.5
69
Overview
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 scenarios 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.
type Point = {
x: number;
y: number;
};
type Label = {
name: string;
};
70
Overview
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.
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 constituent of T has a done property that's
specifically true or false . That's what we meant by each constituent 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;
71
Overview
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 possible 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.
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) {
72
Overview
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.
TypeScript 3.4
// tsconfig.json
{
"compilerOptions": {
"incremental": true,
"outDir": "./lib"
},
"include": ["./src"]
}
73
Overview
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 flag.
// 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 references between different
projects can be built incrementally. As such, composite projects will always produce .tsbuildinfo files.
outFile
When outFile is used, the build information file's name will be based on the output file's name. As an example, 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 --tsBuildInfoFile flag.
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;
}
74
Overview
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;
}
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 variables 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;
}
75
Overview
For more details, you can read more at the original change.
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.
76
Overview
The same way that ordinary tuples are types that extend from Array - a tuple with elements of type T 1 , T 2 , ... T n
extends from Array< T 1 | T 2 | ... T n > - readonly tuples are types that extend from ReadonlyArray . So a
readonly tuple with elements T 1 , T 2 , ... T n extends from ReadonlyArray< T 1 | T 2 | ... T n > .
type Boxify<T> = {
[K in keyof T]: Box<T[K]>
}
// { 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> = {
77
Overview
// { a: string, b: number }
type A = Writable<{
readonly a: string;
readonly b: number
}>;
// number[]
type B = Writable<readonly number[]>;
// [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 assertion with const in
place of the type name (e.g. 123 as const ). When we construct new literal expressions 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 often be omitted.
78
Overview
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 b = Math.random() < 0.5 ?
0 as const :
1 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;
79
Overview
foo.contents.push(5); // ...works!
For more details, you can check out the respective pull request.
TypeScript 3.4 introduces support for type-checking ECMAScript's new globalThis - a global variable that, well, refers to the
global scope. Unlike the above solutions, globalThis provides a standard way for accessing the global scope which can be used
across different environments.
// 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.
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 .
80
Overview
type FruitEater = (fruit: Fruit) => number; // eats and ranks the fruit
type ColorConsumer = (color: Color) => string; // consumes and describes the colors
f("apple"); // error - Argument of type '"red"' is not assignable to parameter of type '"orange"'.
f("red"); // error - Argument of type '"red"' is not assignable to parameter of type '"orange"'.
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" | "orange") 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" .
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;
}
81
Overview
}
else if (animal.kind === "cat") {
animal.catProp;
// ...
}
});
TypeScript 3.0 introduced a new feature for structuring builds called "composite projects". Part of the goal here was to ensure
users could break up large projects into smaller parts that build quickly and preserve project structure, without compromising the
existing TypeScript experience. Thanks to composite projects, TypeScript can use --build mode to recompile only the set of
projects and dependencies. You can think of this as optimizing inter-project builds.
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.
TypeScript 3.2
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 functions 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 declarations 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.
82
Overview
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 either side of a generic
spread expression. For example:
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 signatures, 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:
and Exclude helper types from lib.d.ts , and using the generic type in question as well as the names of the other bindings in
the destructuring pattern.
83
Overview
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:
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
84
Overview
As we mentioned, BigInt support is only available for the esnext target. It may not be obvious, but because 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 targeting esnext .
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.
type Result<T> =
| { error: Error; data: null }
| { error: null; data: T };
{
"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 packages 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.
85
Overview
// @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.
TypeScript 3.1
MapToPromise takes a type T , and when that type is a tuple like Coordinate , only the numeric properties 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
86
Overview
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 addition 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" TypeScript. Additionaly, this approach for property declarations allows us to express common patterns like
defaultProps and propTypes on React stateless function components (SFCs).
FooComponent.defaultProps = {
name: "(anonymous)",
};
[1] More specifically, homomorphic mapped types like in the above form.
Feedback from our community, as well as our own experience, has shown us that leveraging the newest TypeScript features while
also accomodating users on the older versions are difficult. TypeScript introduces a new feature called typesVersions to help
accomodate these scenarios.
When using Node module resolution in TypeScript 3.1, when TypeScript cracks open a package.json file to figure out which
files it needs to read, it first looks at a new field called typesVersions .A package.json with a typesVersions field might
look like this:
{
"name": "package-name",
"version": "1.0",
"types": "./index.d.ts",
"typesVersions": {
">=3.1": { "*": ["ts3.1/*"] }
}
}
This package.json tells TypeScript to check whether the current version of TypeScript is running. If it's 3.1 or later, it 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 today, it works exactly like that.
So in the above example, if we're importing from "package-name" , we'll 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 .
87
Overview
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 .
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": {
">=3.2": { "*": ["ts3.2/*"] },
">=3.1": { "*": ["ts3.1/*"] }
}
}
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/*"] }
}
}
TypeScript 3.0
With these features it becomes possible to strongly type a number of higher-order functions that transform functions and their
parameter lists.
88
Overview
When a rest parameter has a tuple type, the tuple type is expanded into a sequence of discrete parameters. For example the
following two declarations are equivalent:
Example
declare function bind<T, U extends any[], V>(f: (x: T, ...args: U) => V, x: T): (...args: U) => V;
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
89
Overview
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.
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 .
...string[]] means tuples with a number element followed by any number of string elements.
Example
The type of the length property of a tuple type with a rest element is number .
Example
90
Overview
// keyof unknown
91
Overview
x = new Error();
x = x;
x = pAny;
x = pNever;
x = pT;
}
function f25() {
let x: unknown;
let y = x;
}
class C1 {
a: string; // Error
b: unknown;
c: any;
}
92
Overview
TypeScript 3.0 adds supports 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
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 stateless function components (SFCs) use ES2015 default initializers for SFCs:
Changes to @types/React
Corresponding changes to add LibraryManagedAttributes definition to the JSX namespace in @types/React are still needed.
Keep in mind that there are some limitations.
93
Overview
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 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 compiling with --lib
es2017.string .
"foo".padStart(4);
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 representing 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 computed 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
94
Overview
const c = "c";
const d = 10;
const e = Symbol();
const enum E1 { A, B, C }
const enum E2 { 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
};
95
Overview
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
TypeScript 2.9 allows passing generic type arguments to tagged template strings.
Example
96
Overview
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 declared in modules.
Enter import types.
Using import("mod") in a type annotation allows for reaching in a module and accessing its exported declaration without
importing it.
Example
Given a declaration of a class Pet in a module file:
// module.d.ts
// global-script.ts
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}...`);
}
97
Overview
With import types available, many of the visibility errors reported during declaration file generation can be handled by the
compiler without the need to change the input.
For instance:
With TypeScript 2.9, no errors are reported, and now the generated file looks like:
TypeScript 2.9 introduces support for import.meta , a new meta-property as described by the current TC39 proposal.
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 reopening ImportMeta
interface:
// node.d.ts
interface ImportMeta {
__dirname: string;
}
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 importing, extracting
types from and generating .json files.
Example
// settings.json
{
"repo": "TypeScript",
"dry": false,
"debug": false
}
98
Overview
// 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 .
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 .
A conditional type T extends U ? X : Y is either resolved to X or Y , or deferred because the condition depends on one or
more type variables. Whether to resolve or defer is determined as follows:
First, given types T' and U' that are instantiations of T and U where all occurrences of type parameters 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 instantiation 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
99
Overview
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 inferred 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 resolved 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
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
extends U ? X : Y) .
Example
In instantiations of a distributive conditional type T extends U ? X : Y , references to T within the conditional type are
resolved to individual constituents of the union type (i.e. T refers to the individual constituents after the conditional type is
distributed over the union type). Furthermore, references to T within X have an additional type parameter constraint U (i.e.
T is considered assignable to U within X ).
Example
100
Overview
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
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 recursively. For example the
following is an error.
Example
101
Overview
Within the extends clause of a conditional type, it is now possible to have infer declarations that introduce a type variable to
be inferred. Such inferred type variables may be referenced in the true branch of the conditional type. It is possible to have
multiple infer locations for the same type variable.
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:
type Unpacked<T> =
T extends (infer U)[] ? U :
T extends (...args: any[]) => infer U ? U :
T extends Promise<infer U> ? U :
T;
The following example demonstrates how multiple candidates for the same type variable in co-variant positions 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), inferences 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:
102
Overview
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 better 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>> .
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.
103
Overview
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 properties 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 };
104
Overview
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:
105
Overview
TypeScript 2.8 adds support for a per-file configurable JSX factory name using @jsx dom paragma. JSX factory can be
configured for a compilation using --jsxFactory (default is React.createElement ). With TypeScript 2.8 you can override this
on a per-file-basis by adding a comment to the beginning of the file.
Example
Generates:
New --emitDeclarationsOnly
--emitDeclarationsOnly 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.
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
106
Overview
return JSON.stringify(obj);
}
}
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 are 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;
// 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 assignable or comparable to
each other.
Example
107
Overview
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 dependency 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();
}
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 command line to turn off this checking.
For example:
let x: number;
initialize();
console.log(x + x);
// ~ ~
// Error! Variable 'x' is used before being assigned.
function initialize() {
x = 10;
}
108
Overview
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;
In our example, we knew that all uses of x would be initialized so it makes more sense to use definite assignment 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 Tycho
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 tuples 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.
109
Overview
Consider:
const obj = test ? { text: "hello" } : {}; // { text: string } | { text?: undefined }
const s = obj.text; // string | undefined
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 normalized union type:
TypeScript 2.7 improves the handling of structurally identical classes in union types and instanceof expressions:
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 {}
110
Overview
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[])
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
Similarly a default import (i.e. import d from "foo" ) for a CommonJS/AMD/UMD module as equivalent to const d =
require("foo").default . Most of the CommonJS/AMD/UMD modules available today 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.
111
Overview
A namespace import (i.e. import * as foo from "foo" ) is now correctly flagged as uncallabale. 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 converted 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"));
Numeric separators
TypeScript 2.7 brings support for ES Numeric Separators. Numeric literals can now be separated into segments using _ .
Example
112
Overview
TypeScript's --pretty flag can make error messages easier to read and manage. --pretty now uses colors for file names,
diagnostic codes, and line numbers. File names and positions are now also formatted to allow navigation in common terminals
(e.g. Visual Studio Code terminal).
TypeScript 2.6
Under --strictFunctionTypes function type parameter positions are checked contravariantly instead of bivariantly. 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 declarations. Methods are
excluded specifically to ensure generic classes and interfaces (such as Array<T> ) continue 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 assignment 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 contravariant) 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.
Note:
113
Overview
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.
render() {
return (
<div>
Some text.
<h2>A heading</h2>
More text.
</div>
);
}
To address this pattern, React introduced the React.Fragment component, which provides a dedicated way to wrap such
elements without adding an element to the DOM. Correspondingly, the <>...</> syntax was added to JSX to facilitate this new
construct. Therefore, the above scenario becomes:
render() {
return (
<>
Some text.
<h2>A heading</h2>
More text.
</>
);
}
114
Overview
Under --jsx preserve , the new syntax is left untouched for TypeScript emit. Otherwise, for --jsx react , <>...</> is
compiled to React.createElement(React.Fragment, null, ...) , where React.createElement respects --jsxFactory . Note
that it is an error to use <>...</> when --jsx react and --jsxFactory are both enabled.
Please refer to the React blog for more details on fragments and the new syntax.
Example
"use strict";
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = ra
w; }
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 --importHelpers with tslib , an
updated to version 1.8 or later.
Example
115
Overview
c:\ts>tsc --v
Version 2.6.0-dev.20171003
../test/a.ts(1,5): error TS2322: Тип ""string"" не может быть назначен для типа "number".
例: 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.reflec
t' 'es2015.symbol' 'es2015.symbol.wellkno
wn' 'es2016.array.include' 'es2017.object' 'es2017.sharedmemory' 'es2017.string' 'es2017.intl' 'esnext.asyncite
rable'
--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 コンパイルに含む型宣⾔ファイル。
@<ファイル>
116
Overview
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 comments very sparingly.
TypeScript 2.6 brings a faster --watch implementation. The new version optimizes code generation and checking for code bases
using ES modules. Changes detected in a module file will result in only regenerating the changed module, and files that depend on
it, instead of the whole project. Projects with large number of files should reap the most benefit from this change.
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;
}
}
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
}
117
Overview
TypeScript 2.5
As a nice bonus, this can also reduce the memory and runtime footprint of the compiler and language service by avoiding loading
.d.ts files from duplicate packages.
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.
TypeScript 2.4
118
Overview
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);
}
119
Overview
y would have the type any . This meant the program would type-check, but you could technically do anything 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;
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.
120
Overview
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:
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>;
}
121
Overview
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 operator and the array rest
in destructuring assignmnets.
Generators
ES2015 also introduced "Generators", which are functions that can be used to yield partial computation results 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.
for..of statements, Array Destructuring, and Spread elements in Array, Call, and New expressions support 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 iteration 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.
122
Overview
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).
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 simple as: (Symbol as any).asyncIterator =
Symbol.asyncIterator || Symbol.from("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:
123
Overview
declare function create<T extends HTMLElement = HTMLDivElement, U = T[]>(element?: T, children?: U): Container<
T, U>;
The new --strict compiler option represents the recommended setting of a number of type checking options. 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 default have the highest level of type safety
enabled.
124
Overview
Along with setting --strict on by default, tsc --init has an enhanced output. Default tsconfig.json files generated by
tsc --init now include a set of the common compiler options along with their descriptions commented out. Just un-comment
the configuration you like to set to get the desired behavior; we hope the new output simplifies the setting up new projects and
keeps configuration files readable as projects grow.
By default the TypeScript compiler does not report any errors in .js files including using --allowJs . With TypeScript 2.3 type-
checking errors can also be reported in .js files with --checkJs .
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.
TypeScript 2.2
A mixin constructor type refers to a type that has a single construct signature with a single rest argument 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 other 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 {
125
Overview
class Person {
constructor(public name: string) {}
}
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 implements 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 | 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
126
Overview
create(undefined); // Error
The new.target meta-property is new syntax introduced in ES2015. When an instance of a constructor is created via new , the
value of new.target is set to be a reference to the constructor function initially used to allocate the instance. If a function is
called rather than constructed via new , new.target is set to undefined .
new.target comes in handy when Object.setPrototypeOf or __proto__ needs to be set in a class constructor. One such use
case is inheriting from Error in NodeJS v4 and higher.
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.
127
Overview
An operand is considered nullable if the type of the operand is null or undefined or a union type that includes null or
undefined . Note that the union type case only only occurs in --strictNullChecks mode because null and undefined
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 properties on a type using .
notation.
Example
let x: TodoListProps;
React-native build pipeline expects all files to have a .js extensions even if the file contains JSX syntax. The new --jsx
value react-native will persevere the JSX syntax in the output file, but give it a .js extension.
TypeScript 2.1
128
Overview
In JavaScript it is fairly common to have APIs that expect property names as parameters, but so far it hasn't been possible to
express the type relationships that occur in those APIs.
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:
interface Person {
name: string;
age: number;
location: string;
}
129
Overview
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]>;
};
Partial and Readonly , as described earlier, are very useful constructs. You can use them to describe some common JS
routines like:
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 .
130
Overview
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; properties 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};
Note: first, we need to make sure our run-time has an ECMAScript-compliant Promise available globally. 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 flag to something like "dom",
Example
tsconfig.json
{
"compilerOptions": {
"lib": ["dom", "es2015.promise", "es5"]
}
}
dramaticWelcome.ts
131
Overview
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.
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
132
Overview
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 declaration (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
TypeScript 2.1 supports three new target values --target ES2016 , --target ES2017 and --target ESNext .
Using target --target ES2016 will instruct the compiler not to transform ES2016-specific features, e.g. ** operator.
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 assigning 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'.
133
Overview
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[] variable. 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)[]
}
function f2() {
let x = null;
if (cond()) {
x = [];
while (cond()) {
x.push("hello");
}
}
return x; // string[] | null
}
errors are only reported when the compiler is unable to know the type of a variable without a type annotation.
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.
}
}
properties.
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
134
Overview
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"
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:
135
Overview
}(Base));
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.
extends is a new top-level property in tsconfig.json (alongside compilerOptions , files , include , and
exclude ).
The value of extends must be a string containing a path to another configuration file to inherit from.
The configuration from the base file are loaded first, then overridden by those in the inheriting config file.
Circularity between configuration files is not allowed.
files , include and exclude from the inheriting config file overwrite those from the base config file.
All relative paths found in the configuration file will be resolved relative to the configuration file they originated in.
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
136
Overview
Modules are parsed automatically in strict mode. The new flag is recommended for non-module code.
TypeScript 2.0
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.
Example
137
Overview
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 reflect JavaScript semantics (e.g. double-
equals operators check for both values no matter which one is specified whereas triple-equals only checks for the specified value).
Example
interface Options {
location?: {
138
Overview
x?: number;
y?: number;
};
}
Type guards for dotted names also work with user defined type guard functions and the typeof and instanceof 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.
In regular type checking mode the inferred type of z is any because of widening, but in strict null checking mode the inferred
type of z is null (and therefore, absent a type annotation, null is the only possible value for z ).
139
Overview
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 assertion 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
Control flow based type analysis is particuarly 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 .
140
Overview
statements.
Example
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
141
Overview
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.
Because never is a subtype of every type, it is always omitted from union types and it is ignored in function return type
inference as long as there are other types being returned.
142
Overview
Because never is assignable to every type, a function returning never can be used when a callback returning a more specific
type is required:
Read-only properties may have initializers and may be assigned to in constructors within the same class declaration, 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;
143
Overview
constructor() {
this.b = "hello"; // Assignment permitted in constructor
}
}
they expect.
By default the type of this inside a function is any . Starting with TypeScript 2.0, you can provide an explicit 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.
144
Overview
Glob support is here!! Glob support has been one of the most requested features.
Glob-like file patterns are supported two properties "include" and "exclude" .
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 included (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
If the "files" or "include" properties are specified, the compiler will instead include the union of the files included 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 "deployed" to a single
folder at run-time. All module imports with non-relative names are assumed to be relative to the baseUrl .
145
Overview
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.json files.
Example
For instance, an import to a module "jquery" would be translated at runtime to
"node_modules/jquery/dist/jquery.slim.min.js" .
{
"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
Given this project structure:
src
└── views
└── view1.ts (imports './template1')
└── view2.ts
generated
└── 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" .
146
Overview
"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"
]
}
}
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
147
Overview
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);
Example
class Bar {
a: number;
b?: number;
148
Overview
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 undefined 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 undefined 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;
}
}
Example
149
Overview
value = 1;
}
const headers = {
"Content-Type": "application/x-www-form-urlencoded"
};
Getting to ES6/ES2015 built-in API declarations were only limited to target: ES6 . Enter --lib ; with --lib you can specify
a list of built-in API declaration groups that you can chose to include in your project. For instance, if you expect your runtime to
have support for Map , Set and Promise (e.g. most evergreen browsers today), just include --lib
es2015.collection,es2015.promise . Similarly you can exclude declarations you do not want to include in your project, e.g.
DOM if you are working on a node project using --lib es5,es6 .
dom
webworker
es5
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
150
Overview
Example
"compilerOptions": {
"lib": ["es5", "es2015.promise"]
}
TypeScript 2.0 has two new flags to help you maintain a clean code base. --noUnusedParameters flags any unused function or
method parameters errors. --noUnusedLocals flags any unused local (un-exported) declaration like variables, functions, classes,
imports, etc... Also, unused private members of a class would be flagged as errors under --noUnusedLocals .
Example
Parameters declaration with names starting with _ are exempt from the unused parameter checking. e.g.:
function returnNull(_a) { // OK
return null;
}
from "./moduleA.js" , the compiler looked up the definition of "moduleA.js" in ./moduleA.js.ts or ./moduleA.js.d.ts .
This made it hard to use bundling/loading tools like SystemJS that expect URI's in their module identifier.
With TypeScript 2.0, the compiler will look up definition of "moduleA.js" in ./moduleA.ts or ./moduleA.d.ts .
151
Overview
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.
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.
TypeScript 1.8
152
Overview
Example
let x = { a: 1, b: 2, c: 3, d: 4 };
assign(x, { b: 10, d: 20 });
assign(x, { e: 0 }); // Error
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 --
Example
153
Overview
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 --allowUnusedLabels 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.
Example
Example
154
Overview
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.
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.
155
Overview
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 declarations 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 = /*...*/;
// 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[];
}
}
156
Overview
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 literals are, but in type
positions.
Users can now ensure that the type system will catch such errors. Here's our new AnimationOptions using string literal types:
interface AnimationOptions {
deltaX: number;
deltaY: number;
easing: "ease-in" | "ease-out" | "ease-in-out";
}
for T .
Example
157
Overview
Specifying --outFile in conjunction with --module amd or --module system will concatenate all modules in the compilation
into a single output file containing multiple module closures.
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 { };
}
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 compiler will infer the existence of a
default export that has the shape of the entire module itself.
158
Overview
Previously an error, now supported in TypeScript 1.8. let / const declarations within loops and captured in functions are now
emitted to correctly match let / const freshness semantics.
Example
is compiled to:
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
}
159
Overview
in your TS code, like assigning to NaN , will now loudly fail. You can reference the MDN Article on strict mode for a detailed list
of the differences between strict mode and non-strict mode.
Often there are external source files in your project that may not be authored in TypeScript. Alternatively, you might be in the
middle of converting a JS code base into TS, but still want to bundle all your JS code into a single file with the output of your new
TS code.
.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.
Passing --reactNamespace <JSX factory Name> along with --jsx react allows for using a different JSX factory from the
default React .
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 narrowing 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) {}
}
160
Overview
Microsoft.TypeScript.Compiler
Microsoft.TypeScript.MSBuild
Also, a nightly NuGet package to match the nightly npm package is available on https://myget.org:
TypeScript-Preview
We understand that a ton of monochrome output can be a little difficult on the eyes. Colors can help discern where a message
starts and ends, and these visual clues are important when error output gets overwhelming.
By just passing the --pretty command line option, TypeScript gives more colorful output with context about where things are
going wrong.
161
Overview
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 directory named
tsconfig.json .
162
Overview
{
"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/stdout .
As an example, we can pipe our emitted JavaScript into a pretty printer like pretty-js:
163
Overview
We also disable the project properties page when you add a tsconfig.json file. This means that all configuration 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.
TypeScript 1.7
Example
In the following example, each input element will be printed out one at a time with a 200ms delay:
"use strict";
// printDelayed is a 'Promise<void>'
async function printDelayed(elements: string[]) {
for (const element of elements) {
await delay(200);
console.log(element);
}
}
TypeScript 1.7 adds ES6 to the list of options available for the --module flag and allows you to specify the module output
when targeting ES6 . This provides more flexibility to target exactly the features you want in specific runtimes.
Example
{
"compilerOptions": {
"module": "amd",
"target": "es6"
164
Overview
}
}
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 wanted 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);
165
Overview
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);
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
166
Overview
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 default 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 });
TypeScript 1.6
JSX support
JSX is an embeddable XML-like syntax. It is meant to be transformed into valid JavaScript, but the semantics 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 compiling JSX directly into JavaScript.
167
Overview
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;
}
Output generation
TypeScript ships with two JSX modes: preserve and react .
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.
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
168
Overview
var n = x.b;
interface Person {
name: string;
}
interface A { a: string }
interface B { b: string }
interface C { c: string }
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;
}
169
Overview
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();
let x = v.x; // number
let y = v.y; // string
}
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 function 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 expression optionally
followed by a type argument list. The type of the expression must be a constructor function 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:
170
Overview
class ThingA {
getGreeting() { return "Hello from A"; }
}
class ThingB {
getGreeting() { return "Hello from B"; }
}
interface Greeter {
getGreeting(): string;
}
interface GreeterConstructor {
new (): Greeter;
}
Examples
171
Overview
var s: Lazy<string>;
s = "eager";
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
}
172
Overview
An async function is a function or method that has been prefixed with the async modifier. This modifier informs 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 annotation 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:
173
Overview
interface Foo {
y : string;
}
Examples
{
"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.
174
Overview
TypeScript 1.5
ES6 Modules
TypeScript 1.5 supports ECMAScript 6 (ES6) modules. ES6 modules are effectively TypeScript external modules 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 internal 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 introducing 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
175
Overview
An export default declaration specifies an expression that becomes the default export of a module:
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 extracted 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
176
Overview
Destructuring patterns can also be used in regular assignment expressions. For instance, swapping two variables can be written as
a single destructuring assignment:
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
}
for..of support
177
Overview
TypeScript 1.5 adds support to ES6 for..of loops on arrays for ES3/ES5 as well as full support for Iterator interfaces when
targetting ES6.
Example
The TypeScript compiler will transpile for..of arrays to idiomatic ES3/ES5 JavaScript when targeting those versions:
Decorators
TypeScript decorators are based on the ES7 decorator proposal.
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 installed 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() { }
}
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:
178
Overview
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
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" .
179
Overview
will be emitted as
The new amd-dependency name property allows passing an optional name for an amd-dependency:
Generated JS code:
Adding 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. A project is compiled in one of the following
ways:
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,
}
}
180
Overview
Option --outDir duplicates the input hierarchy in the output. The compiler computes the root of the input files as the longest
common path of all input files; and then uses that to replicate all its substructure in the output.
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.
command line flag allows overriding this behavior and specifying the new line character to be used in generated output files.
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 function 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:
181
Overview
if (opts.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');
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 confusing bugs:
182
Overview
The new ES6 keyword let , now supported in TypeScript, declares a variable with more intuitive "block" semantics. 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
}
const declarations
The other new ES6 declaration type supported in TypeScript is const .A const variable may not be assigned 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.
183
Overview
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;
184
Overview
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;
});
TypeScript 1.3
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
185
Overview
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
TypeScript 1.1
Performance Improvements
The 1.1 compiler is typically around 4x faster than any previous release. See this blog post for some impressive 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 */
}
}
186
TypeScript-3.7
Optional Chaining
Nullish Coalescing
Assertion Functions
Better Support for never -Returning Functions
(More) Recursive Type Aliases
--declaration and --allowJs
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 expressions if we run into a
null or undefined . The star of the show in optional chaining is the new ?. operator 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) {
// ...
}
187
TypeScript-3.7
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];
// 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 undefined .
return result;
}
The "short-circuiting" behavior that optional chains have is limited property accesses, calls, element accesses - 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
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.
188
TypeScript-3.7
Nullish Coalescing
Playground
The nullish coalescing operator is another upcoming ECMAScript feature that goes hand-in-hand with optional 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
this is a new way to say that the value foo will be used when it's "present"; but when it's null or undefined , calculate
bar() in its place.
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 implementing 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;
}
189
TypeScript-3.7
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.
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 assertion 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 specific variable or property
has a different type.
190
TypeScript-3.7
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.
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.
191
TypeScript-3.7
return doThingWithNumber(x);
}
return process.exit(1);
}
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 following causes an error.
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.
192
TypeScript-3.7
That's exactly what TypeScript 3.7 introduces. At the "top level" of a type alias, TypeScript will defer resolving type arguments to
permit these patterns.
This means that code like the following that was trying to represent JSON...
type Json =
| string
| number
| boolean
| null
| JsonObject
| JsonArray;
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.
type VirtualNode =
| string
| [string, { [key: string]: any }, ...VirtualNode[]];
For more information, you can read up on the original pull request.
The --declaration flag in TypeScript allows us to generate .d.ts files (declaration files) from TypeScript source files (i.e.
.ts and .tsx files). These .d.ts files are important for a couple of reasons.
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 underappreciated: 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 --declaration flag when migrating codebases, even if
they were JSDoc-annotated. TypeScript 3.7 changes that, and allows the two options to be used together!
193
TypeScript-3.7
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 understand 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 figures 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.
*
* @param input {Uint8Array}
* @param width {number}
* @param height {number}
*/
export function blurImage(input: Uint8Array, width: number, height: number): Uint8Array;
This can go beyond basic functions with @param tags too, where the following example:
/**
* @callback Job
* @returns {void}
*/
194
TypeScript-3.7
push(work) {
if (this.queue.length + 1 > this.depthLimit) throw new Error("Queue full!");
this.queue.push(work);
}
/**
* Starts the queue if it has not yet started
*/
start() {
if (this.started) return false;
this.started = true;
while (this.queue.length) {
/** @type {Job} */(this.queue.shift())();
}
return true;
}
}
/**
* @callback Job
* @returns {void}
*/
/** Queues work */
export class Worker {
constructor(maxDepth?: number);
started: boolean;
depthLimit: number;
/**
* 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;
}
195
TypeScript-3.7
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,
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 incrementally to help users
mitigate potential future breakage. We've provided a new flag called useDefineForClassFields 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.
class AnimalHouse {
resident: Animal;
constructor(animal: Animal) {
this.resident = animal;
}
196
TypeScript-3.7
constructor(dog: Dog) {
super(dog);
}
}
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 indicate that a property
should have no emit.
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 seperate project that targets ES5 and uses --noEmit to
avoid a full build.
197
TypeScript-3.7
For more information, you can take a look at the original pull request for these changes.
We strongly encourage users to try the useDefineForClassFields flag and report back on our issue tracker 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 experience 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!
This check is a breaking change, but for that reason the checks are very conservative. This error is only issued 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 :
198
TypeScript-3.7
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 undefined / null , or use
!! to write something like if (!!user.isAdministrator) to indicate that the coercion is intentional.
We owe a big thanks to GitHub user @jwbay who took the initiative to create a proof-of-concept and iterated to provide us with
with the current version.
199
TypeScript-3.7
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:
To understand the full impact, read the section above on the useDefineForClassFields flag.
200
TypeScript-3.7
As mentioned above, TypeScript now errors when functions appear to be uncalled within if statement conditions. An error is
issued when a function type is checked in if conditions unless any of the following apply:
// ./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 conflict is unintentional and the best fix is to
rename the imported type. If the intent was to augment the imported type, a proper module augmentation should be written
instead.
201
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.
To allow differentiation between returned values and yielded values, TypeScript 3.6 converts the IteratorResult type to a
discriminated union type:
202
TypeScript-3.6
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!";
}
// prints:
//
// 0
// 1
// 2
// 3
// 4
// 5
// DONE!
203
TypeScript-3.6
For more details on the change, see the pull request here.
-downlevelIteration flag. The looser default without --downlevelIteration works fairly well; however, there were some
common cases where the transformation of array spreads had observable differences. For example, the following array containing
a spread
[...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;
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.
204
TypeScript-3.6
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)
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.
205
TypeScript-3.6
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.
TypeScript 3.0 introduced support for referencing other and building them incrementally using the --build flag. Additionally,
TypeScript 3.4 introduced the --incremental flag for saving information about previous compilations to only rebuild certain
files. These flags were incredibly useful for structuring projects more flexibly and speeding builds up. Unfortunately, using these
flags didn't work with 3rd party build tools like Gulp and Webpack. TypeScript 3.6 now exposes two sets of APIs to operate on
project references and incremental program building.
For creating --incremental builds, users can leverage the createIncrementalProgram and createIncrementalCompilerHost
APIs. Users can also re-hydrate old program instances from .tsbuildinfo files generated 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 returns an instance of the
new type SolutionBuilder .
For more details on these APIs, you can see the original pull request.
206
TypeScript-3.6
Editors like Visual Studio and Visual Studio Code can automatically apply quick fixes, refactorings, and other transformations
like automatically importing values from other modules. These transformations are powered by TypeScript, and older versions of
TypeScript unconditionally added semicolons to the end of every statement; unfortunately, this disagreed with many users' style
guidelines, and many users were displeased with the editor inserting semicolons.
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.
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!
207
TypeScript-3.5
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 scenarios 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.
type Point = {
x: number;
y: number;
};
type Label = {
208
TypeScript-3.5
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.
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 constituent of T has a done property that's
specifically true or false . That's what we meant by each constituent 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 {
209
TypeScript-3.5
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 possible 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.
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";
210
TypeScript-3.5
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.
211
TypeScript-3.4
// 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 flag.
// 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 references between different
projects can be built incrementally. As such, composite projects will always produce .tsbuildinfo files.
outFile
When outFile is used, the build information file's name will be based on the output file's name. As an example, 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 --tsBuildInfoFile flag.
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));
}
212
TypeScript-3.4
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;
}
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 variables 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.
213
TypeScript-3.4
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.
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.
214
TypeScript-3.4
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 > .
type Boxify<T> = {
[K in keyof T]: Box<T[K]>
}
// { 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.
215
TypeScript-3.4
// 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[]>;
// [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 assertion with const in
place of the type name (e.g. 123 as const ). When we construct new literal expressions 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;
216
TypeScript-3.4
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 often be omitted.
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.
217
TypeScript-3.4
// Works!
let b = Math.random() < 0.5 ?
0 as const :
1 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;
foo.contents.push(5); // ...works!
For more details, you can check out the respective pull request.
TypeScript 3.4 introduces support for type-checking ECMAScript's new globalThis - a global variable that, well, refers to the
global scope. Unlike the above solutions, globalThis provides a standard way for accessing the global scope which can be used
across different environments.
// 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.
218
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
f("apple"); // error - Argument of type '"apple"' is not assignable to parameter of type '"orange"'.
f("red"); // error - Argument of type '"red"' is not assignable to parameter of type '"orange"'.
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" | "orange") 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" .
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;
}
219
TypeScript-3.3
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;
}
TypeScript 3.0 introduced a new feature for structuring builds called "composite projects". Part of the goal here was to ensure
users could break up large projects into smaller parts that build quickly and preserve project structure, without compromising the
existing TypeScript experience. Thanks to composite projects, TypeScript can use --build mode to recompile only the set of
projects and dependencies. You can think of this as optimizing inter-project builds.
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.
220
TypeScript-3.2
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 functions 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 declarations 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 either side of a generic
spread expression. For example:
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 signatures, 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:
221
TypeScript-3.2
and Exclude helper types from lib.d.ts , and using the generic type in question as well as the names of the other bindings in
the destructuring pattern.
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:
fibonacci(10000n)
While you might imagine close interaction between number and bigint , the two are separate domains.
222
TypeScript-3.2
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 because 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 targeting esnext .
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.
type Result<T> =
| { error: Error; data: null }
| { error: null; data: T };
223
TypeScript-3.2
{
"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 packages 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.
// @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.
224
TypeScript-3.1
MapToPromise takes a type T , and when that type is a tuple like Coordinate , only the numeric properties 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
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 addition 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" TypeScript. Additionally, this approach for property declarations allows us to express common patterns like
defaultProps and propTypes on React function components (formerly known as SFCs).
FooComponent.defaultProps = {
name: "(anonymous)",
};
[1]
More specifically, homomorphic mapped types like in the above form.
225
TypeScript-3.1
Feedback from our community, as well as our own experience, has shown us that leveraging the newest TypeScript features while
also accommodating users on the older versions are difficult. TypeScript introduces a new feature called typesVersions to help
accommodate these scenarios.
When using Node module resolution in TypeScript 3.1, when TypeScript cracks open a package.json file to figure out which
files it needs to read, it first looks at a new field called typesVersions .A package.json with a typesVersions field might
look like this:
{
"name": "package-name",
"version": "1.0",
"types": "./index.d.ts",
"typesVersions": {
">=3.1": { "*": ["ts3.1/*"] }
}
}
This package.json tells TypeScript to check whether the current version of TypeScript is running. If it's 3.1 or later, it 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 today, it works exactly like that.
So in the above example, if we're importing from "package-name" , we'll 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 .
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": {
">=3.2": { "*": ["ts3.2/*"] },
">=3.1": { "*": ["ts3.1/*"] }
}
}
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",
226
TypeScript-3.1
"typesVersions": {
// NOTE: this doesn't work!
">=3.1": { "*": ["ts3.1/*"] },
">=3.2": { "*": ["ts3.2/*"] }
}
}
227
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.
228
TypeScript-3.0
A rest parameter is permitted to have a generic type that is constrained to an array type, and type inference can infer tuple types
for such generic rest parameters. This enables higher-order capturing and spreading of partial parameter lists:
Example
declare function bind<T, U extends any[], V>(f: (x: T, ...args: U) => V, x: T): (...args: U) => V;
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.
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 .
...string[]] means tuples with a number element followed by any number of string elements.
Example
229
TypeScript-3.0
return args;
}
The type of the length property of a tuple type with a rest element is number .
Example
// keyof unknown
230
TypeScript-3.0
-x; // Error
+x; // Error
}
231
TypeScript-3.0
function f25() {
let x: unknown;
let y = x;
}
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 .
232
TypeScript-3.0
}
static defaultProps = { name: "world"};
}
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:
Changes to @types/React
Corresponding changes to add LibraryManagedAttributes definition to the JSX namespace in @types/React are still needed.
Keep in mind that there are some limitations.
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 compiling with --lib
es2017.string .
"foo".padStart(4);
233
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 representing 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 computed 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 { 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
}
234
TypeScript-2.9
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
};
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:
235
TypeScript-2.9
If your functions are open to handling all property keys, then the changes should be done down-stream:
Example
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 declared in modules.
Enter import types.
236
TypeScript-2.9
Using import("mod") in a type annotation allows for reaching in a module and accessing its exported declaration without
importing it.
Example
// module.d.ts
// global-script.ts
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:
TypeScript 2.9 introduces support for import.meta , a new meta-property as described by the current TC39 proposal.
237
TypeScript-2.9
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 reopening ImportMeta
interface:
// node.d.ts
interface ImportMeta {
__dirname: string;
}
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 importing, 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
}
}
238
TypeScript-2.9
Starting TypeScript 2.9 errors are displayed under --pretty by default if the output device is applicable for colorful text.
TypeScript will check if the output steam has isTty property set.
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 .
239
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 .
A conditional type T extends U ? X : Y is either resolved to X or Y , or deferred because the condition depends on one or
more type variables. Whether to resolve or defer is determined as follows:
First, given types T' and U' that are instantiations of T and U where all occurrences of type parameters 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 instantiation 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 inferred 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 resolved 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
type TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
extends U ? X : Y) .
Example
240
TypeScript-2.8
In instantiations of a distributive conditional type T extends U ? X : Y , references to T within the conditional type are
resolved to individual constituents of the union type (i.e. T refers to the individual constituents after the conditional type is
distributed over the union type). Furthermore, references to T within X have an additional type parameter constraint U (i.e.
T is considered assignable to U within X ).
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
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;
241
TypeScript-2.8
Similar to union and intersection types, 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:
Conditional types can be nested to form a sequence of pattern matches that are evaluated in order:
type Unpacked<T> =
T extends (infer U)[] ? U :
T extends (...args: any[]) => infer U ? U :
T extends Promise<infer U> ? U :
T;
The following example demonstrates how multiple candidates for the same type variable in co-variant positions 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), inferences 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.
242
TypeScript-2.8
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:
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;
}
243
TypeScript-2.8
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 better 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>> .
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 properties 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 };
244
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:
245
TypeScript-2.8
}
};
C.prototype.q = function(r) {
return this.p === r;
};
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.
246
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;
247
TypeScript-2.7
// 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 assignable 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 dependency 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();
}
initialize() {
this.foo = 0;
}
}
248
TypeScript-2.7
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 command 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;
In our example, we knew that all uses of x would be initialized so it makes more sense to use definite assignment assertions than
non-null assertions.
249
TypeScript-2.7
In TypeScript 2.7, tuples of different arities are no longer assignable to each other. Thanks to a pull request from Tycho
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 tuples 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:
const obj = test ? { text: "hello" } : {}; // { text: string } | { text?: undefined }
const s = obj.text; // string | undefined
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 normalized union type:
250
TypeScript-2.7
TypeScript 2.7 improves the handling of structurally identical classes in union types and instanceof expressions:
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[])
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;
}
251
TypeScript-2.7
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
Similarly a default import (i.e. import d from "foo" ) for a CommonJS/AMD/UMD module as equivalent to const d =
require("foo").default .Most of the CommonJS/AMD/UMD modules available today 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 converted 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"));
Numeric separators
252
TypeScript-2.7
TypeScript 2.7 brings support for ES Numeric Separators. Numeric literals can now be separated into segments using _ .
Example
253
TypeScript-2.6
Under --strictFunctionTypes function type parameter positions are checked contravariantly instead of bivariantly. 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 declarations. Methods are
excluded specifically to ensure generic classes and interfaces (such as Array<T> ) continue 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 assignment 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 contravariant) 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.
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;
}
254
TypeScript-2.6
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
"use strict";
var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) {
if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = ra
w; }
return cooked;
};
function id(x) {
return x;
}
var _a;
function templateObjectFactory() {
return id(_a || (_a = __makeTemplateObject(["hello world"], ["hello world"])));
}
255
TypeScript-2.6
Note: This change brings a new emit helper, __makeTemplateObject ; if you are using --importHelpers 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".
例: 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.reflec
t' 'es2015.symbol' 'es2015.symbol.wellkno
wn' 'es2016.array.include' 'es2017.object' 'es2017.sharedmemory' 'es2017.string' 'es2017.intl' 'esnext.asyncite
rable'
--allowJs javascript ファイルのコンパイルを許可します。
--jsx 種類 JSX コード⽣成を指定します: 'preserve'、'react-native'、'react'。
-d, --declaration 対応する '.d.ts' ファイルを⽣成します。
--sourceMap 対応する '.map' ファイルを⽣成します。
--outFile ファイル 出⼒を連結して 1 つのファイルを⽣成します。
--outDir ディレクトリ ディレクトリへ出⼒構造をリダイレクトします。
256
TypeScript-2.6
--removeComments コメントを出⼒しないでください。
--noEmit 出⼒しないでください。
--strict strict 型チェックのオプションをすべて有効にします。
--noImplicitAny 暗黙的な 'any' 型を含む式と宣⾔に関するエラーを発⽣させます。
--strictNullChecks 厳格な null チェックを有効にします。
--noImplicitThis 暗黙的な 'any' 型を持つ 'this' 式でエラーが発⽣します。
--alwaysStrict 厳格モードで解析してソース ファイルごとに "use strict" を⽣成します。
--noUnusedLocals 使⽤されていないローカルに関するエラーを報告します。
--noUnusedParameters 使⽤されていないパラメーターに関するエラーを報告します。
--noImplicitReturns 関数の⼀部のコード パスが値を返さない場合にエラーを報告します。
--noFallthroughCasesInSwitch switch ステートメントに case のフォールスルーがある場合にエラーを報告します。
--types コンパイルに含む型宣⾔ファイル。
@<ファイル>
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 comments very sparingly.
TypeScript 2.6 brings a faster --watch implementation. The new version optimizes code generation and checking for code bases
using ES modules. Changes detected in a module file will result in only regenerating the changed module, and files that depend on
it, instead of the whole project. Projects with large number of files should reap the most benefit from this change.
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() {
257
TypeScript-2.6
this.m = 0;
}
}
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
}
258
TypeScript-2.5
As a nice bonus, this can also reduce the memory and runtime footprint of the compiler and language service by avoiding loading
.d.ts files from duplicate packages.
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.
259
TypeScript-2.4
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);
}
260
TypeScript-2.4
y would have the type any . This meant the program would type-check, but you could technically do anything 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;
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.
261
TypeScript-2.4
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:
262
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 operator and the array rest
in destructuring assignmnets.
Generators
ES2015 also introduced "Generators", which are functions that can be used to yield partial computation results 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.
for..of statements, Array Destructuring, and Spread elements in Array, Call, and New expressions support 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
263
TypeScript-2.3
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 iteration 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:
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 simple 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.
264
TypeScript-2.3
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:
declare function create<T extends HTMLElement = HTMLDivElement, U = T[]>(element?: T, children?: U): Container<
T, U>;
The new --strict compiler option represents the recommended setting of a number of type checking options. 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,
265
TypeScript-2.3
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 default have the highest level of type safety
enabled.
By default the TypeScript compiler does not report any errors in .js files including using --allowJs . With TypeScript 2.3 type-
checking errors can also be reported in .js files with --checkJs .
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.
266
TypeScript-2.2
A mixin constructor type refers to a type that has a single construct signature with a single rest argument 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 other 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) {}
}
267
TypeScript-2.2
customer._tag = "test";
customer.accountBalance = 0;
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 implements 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
The new.target meta-property is new syntax introduced in ES2015. When an instance of a constructor is created via new , the
value of new.target is set to be a reference to the constructor function initially used to allocate the instance. If a function is
called rather than constructed via new , new.target is set to undefined .
new.target comes in handy when Object.setPrototypeOf or __proto__ needs to be set in a class constructor. One such use
case is inheriting from Error in NodeJS v4 and higher.
Example
268
TypeScript-2.2
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.
If the operand of a + , - , ~ , ++ , or -- unary operator is nullable.
An operand is considered nullable if the type of the operand is null or undefined or a union type that includes null or
undefined . Note that the union type case only only occurs in --strictNullChecks mode because null and undefined
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 properties on a type using .
notation.
269
TypeScript-2.2
Example
let x: TodoListProps;
React-native build pipeline expects all files to have a .js extensions even if the file contains JSX syntax. The new --jsx
value react-native will persevere the JSX syntax in the output file, but give it a .js extension.
270
TypeScript-2.1
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 :
interface Person {
name: string;
age: number;
271
TypeScript-2.1
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]>;
};
Partial and Readonly , as described earlier, are very useful constructs. You can use them to describe some common JS
routines like:
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 .
272
TypeScript-2.1
function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U>
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; properties 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};
Note: first, we need to make sure our run-time has an ECMAScript-compliant Promise available globally. 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 flag to something like "dom",
Example
tsconfig.json
{
"compilerOptions": {
"lib": ["dom", "es2015.promise", "es5"]
}
}
273
TypeScript-2.1
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.
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);
274
TypeScript-2.1
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 declaration (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
TypeScript 2.1 supports three new target values --target ES2016 , --target ES2017 and --target ESNext .
Using target --target ES2016 will instruct the compiler not to transform ES2016-specific features, e.g. ** operator.
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 assigning 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'.
275
TypeScript-2.1
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[] variable. 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)[]
}
function f2() {
let x = null;
if (cond()) {
x = [];
while (cond()) {
x.push("hello");
}
}
return x; // string[] | null
}
errors are only reported when the compiler is unable to know the type of a variable without a type annotation.
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.
}
}
properties.
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.
276
TypeScript-2.1
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"
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:
277
TypeScript-2.1
}
return Derived;
}(Base));
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.
extends is a new top-level property in tsconfig.json (alongside compilerOptions , files , include , and
exclude ).
The value of extends must be a string containing a path to another configuration file to inherit from.
The configuration from the base file are loaded first, then overridden by those in the inheriting config file.
Circularity between configuration files is not allowed.
files , include and exclude from the inheriting config file overwrite those from the base config file.
All relative paths found in the configuration file will be resolved relative to the configuration file they originated in.
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
278
TypeScript-2.1
Modules are parsed automatically in strict mode. The new flag is recommended for non-module code.
279
TypeScript-2.0
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
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.
Example
280
TypeScript-2.0
x = 1;
y = null;
x; // Ok
y; // Ok
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 reflect JavaScript semantics (e.g. double-
equals operators check for both values no matter which one is specified whereas triple-equals only checks for the specified value).
Example
interface Options {
location?: {
x?: number;
y?: number;
};
}
281
TypeScript-2.0
Type guards for dotted names also work with user defined type guard functions and the typeof and instanceof 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.
In regular type checking mode the inferred type of z is any because of widening, but in strict null checking mode the inferred
type of z is null (and therefore, absent a type annotation, null is the only possible value for z ).
282
TypeScript-2.0
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 assertion 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
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 .
283
TypeScript-2.0
statements.
Example
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
284
TypeScript-2.0
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.
Because never is a subtype of every type, it is always omitted from union types and it is ignored in function return type
inference as long as there are other types being returned.
285
TypeScript-2.0
Because never is assignable to every type, a function returning never can be used when a callback returning a more specific
type is required:
Read-only properties may have initializers and may be assigned to in constructors within the same class declaration, 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
286
TypeScript-2.0
}
}
they expect.
By default the type of this inside a function is any . Starting with TypeScript 2.0, you can provide an explicit 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 support is here!! Glob support has been one of the most requested features.
287
TypeScript-2.0
Glob-like file patterns are supported two properties "include" and "exclude" .
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 included (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
If the "files" or "include" properties are specified, the compiler will instead include the union of the files included 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 "deployed" to a single
folder at run-time. All module imports with non-relative names are assumed to be relative to the baseUrl .
288
TypeScript-2.0
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.json 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.
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 were merged together in one directory.
Example
src
└── views
└── view1.ts (imports './template1')
└── view2.ts
generated
└── templates
└── views
└── template1.ts (imports './view2')
289
TypeScript-2.0
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 example, 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
290
TypeScript-2.0
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);
Example
class Bar {
a: number;
291
TypeScript-2.0
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 undefined 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 undefined 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;
}
}
Example
292
TypeScript-2.0
value = 1;
}
const headers = {
"Content-Type": "application/x-www-form-urlencoded"
};
Getting to ES6/ES2015 built-in API declarations were only limited to target: ES6 . Enter --lib ; with --lib you can specify
a list of built-in API declaration groups that you can chose to include in your project. For instance, if you expect your runtime to
have support for Map , Set and Promise (e.g. most evergreen browsers today), just include --lib
es2015.collection,es2015.promise . Similarly you can exclude declarations you do not want to include in your project, e.g.
DOM if you are working on a node project using --lib es5,es6 .
dom
webworker
es5
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
293
TypeScript-2.0
Example
"compilerOptions": {
"lib": ["es5", "es2015.promise"]
}
TypeScript 2.0 has two new flags to help you maintain a clean code base. --noUnusedParameters flags any unused function or
method parameters errors. --noUnusedLocals flags any unused local (un-exported) declaration like variables, functions, classes,
imports, etc... Also, unused private members of a class would be flagged as errors under --noUnusedLocals .
Example
Parameters declaration with names starting with _ are exempt from the unused parameter checking. e.g.:
function returnNull(_a) { // OK
return null;
}
from "./moduleA.js" , the compiler looked up the definition of "moduleA.js" in ./moduleA.js.ts or ./moduleA.js.d.ts .
This made it hard to use bundling/loading tools like SystemJS that expect URI's in their module identifier.
With TypeScript 2.0, the compiler will look up definition of "moduleA.js" in ./moduleA.ts or ./moduleA.d.t .
294
TypeScript-2.0
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.
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.
295
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
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 --
Example
296
TypeScript-1.8
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 --allowUnusedLabels 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.
Example
Example
297
TypeScript-1.8
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.
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.
298
TypeScript-1.8
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 declarations 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 = /*...*/;
// 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[];
}
}
299
TypeScript-1.8
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 literals are, but in type
positions.
Users can now ensure that the type system will catch such errors. Here's our new AnimationOptions using string literal types:
interface AnimationOptions {
deltaX: number;
deltaY: number;
easing: "ease-in" | "ease-out" | "ease-in-out";
}
for T .
Example
300
TypeScript-1.8
Specifying --outFile in conjunction with --module amd or --module system will concatenate all modules in the compilation
into a single output file containing multiple module closures.
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 { };
}
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 compiler will infer the existence of a
default export that has the shape of the entire module itself.
301
TypeScript-1.8
Previously an error, now supported in TypeScript 1.8. let / const declarations within loops and captured in functions are now
emitted to correctly match let / const freshness semantics.
Example
is compiled to:
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
}
302
TypeScript-1.8
in your TS code, like assigning to NaN , will now loudly fail. You can reference the MDN Article on strict mode for a detailed list
of the differences between strict mode and non-strict mode.
Often there are external source files in your project that may not be authored in TypeScript. Alternatively, you might be in the
middle of converting a JS code base into TS, but still want to bundle all your JS code into a single file with the output of your new
TS code.
.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.
Passing --reactNamespace <JSX factory Name> along with --jsx react allows for using a different JSX factory from the
default React .
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) {}
}
303
TypeScript-1.8
Microsoft.TypeScript.Compiler
Microsoft.TypeScript.MSBuild
Also, a nightly NuGet package to match the nightly npm package is available on myget:
TypeScript-Preview
We understand that a ton of monochrome output can be a little difficult on the eyes. Colors can help discern where a message
starts and ends, and these visual clues are important when error output gets overwhelming.
By just passing the --pretty command line option, TypeScript gives more colorful output with context about where things are
going wrong.
304
TypeScript-1.8
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 directory named
tsconfig.json .
305
TypeScript-1.8
{
"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/stdout .
As an example, we can pipe our emitted JavaScript into a pretty printer like pretty-js:
306
TypeScript-1.8
We also disable the project properties page when you add a tsconfig.json file. This means that all configuration 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.
307
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);
}
}
TypeScript 1.7 adds ES6 to the list of options available for the --module flag and allows you to specify the module output
when targeting ES6 . This provides more flexibility to target exactly the features you want in specific runtimes.
Example
{
"compilerOptions": {
"module": "amd",
"target": "es6"
}
}
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:
308
TypeScript-1.7
This often opens up very elegant ways of writing code; however, there was a problem for classes that wanted 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);
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:
309
TypeScript-1.7
.divide(2)
.sin() // Error: 'BasicCalculator' has no 'sin' method.
.currentValue();
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);
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 default value in the array
binding pattern and are automatically added to the array literal type.
Example
310
TypeScript-1.7
f2();
f2({}); // Error, x not optional
f2({ x: 1 });
f2({ y: 1 }); // Error, x not optional
f2({ x: 1, y: 1 });
311
TypeScript-1.6
JSX support
JSX is an embeddable XML-like syntax. It is meant to be transformed into valid JavaScript, but the semantics 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 compiling JSX directly into JavaScript.
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;
}
Output generation
TypeScript ships with two JSX modes: preserve and react .
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.
See the [[JSX]] wiki page for more information on using JSX in TypeScript.
Intersection types
312
TypeScript-1.6
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 }
function f() {
if (true) {
interface T { x: number }
let v: T;
v.x = 5;
}
else {
interface T { x: string }
let v: T;
313
TypeScript-1.6
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();
let x = v.x; // number
let y = v.y; // string
}
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 function 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:
314
TypeScript-1.6
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 expression optionally
followed by a type argument list. The type of the expression must be a constructor function 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 {
getGreeting(): string;
}
interface GreeterConstructor {
new (): Greeter;
}
Examples
315
TypeScript-1.6
var s: Lazy<string>;
s = "eager";
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:
316
TypeScript-1.6
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
}
An async function is a function or method that has been prefixed with the async modifier. This modifier informs 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 annotation 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;
}
317
TypeScript-1.6
Nightly builds
While not strictly a language change, nightly builds are now available by installing with the following command:
interface Foo {
y : string;
}
Examples
318
TypeScript-1.6
{
"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.
319
TypeScript-1.5
ES6 Modules
TypeScript 1.5 supports ECMAScript 6 (ES6) modules. ES6 modules are effectively TypeScript external modules 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 internal 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 introducing 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:
320
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 extracted 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:
var x = 1;
var y = 2;
[x, y] = [y, x];
321
TypeScript-1.5
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
}
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 interfaces when
targeting ES6.
Example
The TypeScript compiler will transpile for..of arrays to idiomatic ES3/ES5 JavaScript when targeting those versions:
322
TypeScript-1.5
Decorators
TypeScript decorators are based on the ES7 decorator proposal.
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 installed 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() { ... }
}
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:
323
TypeScript-1.5
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
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
324
TypeScript-1.5
}
(_a = ["Hello \n", " \t ", "\n world"], _a.raw = ["Hello \\n", " \\t ", "\\n world"], oddRawStrings(_a, 123, 45
6));
var _a;
The new amd-dependency name property allows passing an optional name for an amd-dependency:
Generated JS code:
Adding 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. A project is compiled in one of the following
ways:
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\ .
325
TypeScript-1.5
--rootDir specifies the input directory to be mirrored in output instead of computing it.
command line flag allows overriding this behavior and specifying the new line character to be used in generated output files.
326
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 function 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.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');
With union types, you can now specify the desired behavior at both the function declaration site and the call site:
327
TypeScript-1.4
let declarations
In JavaScript, var declarations are "hoisted" to the top of their enclosing scope. This can result in confusing bugs:
The new ES6 keyword let , now supported in TypeScript, declares a variable with more intuitive "block" semantics. 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
}
const declarations
The other new ES6 declaration type supported in TypeScript is const .A const variable may not be assigned 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:
328
TypeScript-1.4
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.
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.
329
TypeScript-1.4
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;
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;
})();
330
TypeScript-1.4
exports.C = C;
});
331
TypeScript-1.3
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
332
TypeScript-1.1
Performance Improvements
The 1.1 compiler is typically around 4x faster than any previous release. See this blog post for some impressive 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 */
}
}
333
Handbook
Handbook
Basic Types
Variable Declarations
Interfaces
Classes
Functions
Generics
Enums
Type Inference
Type Compatibility
Advanced Types
Symbols
Iterators and Generators
Modules
Namespaces
Namespaces and Modules
Module Resolution
Declaration Merging
JSX
Decorators
Mixins
Triple-Slash Directives
334
Basic Types
Table of Contents
1. Introduction
2. Boolean
3. Number
4. String
5. Array
6. Tuple
7. Enum
8. Any
9. Void
10. Null and Undefined
11. Never
12. Object
13. Type assertions
14. A note about 'let'
Introduction
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 much the same types as you would expect in JavaScript, with a convenient
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 floating point values. These floating point numbers get the type number . In
addition to hexadecimal and decimal literals, TypeScript also supports binary and octal literals introduced in ECMAScript 2015.
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.
335
Basic Types
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 } .
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 number :
When accessing an element with a known index, the correct type is retrieved:
console.log(x[0].substring(1)); // OK
console.log(x[1].substring(1)); // Error, 'number' does not have 'substring'
Accessing an element outside the set of known indices fails with an error:
x[3] = "world"; // Error, Property '3' does not exist on type '[string, number]'.
console.log(x[5].toString()); // Error, Property '5' does not exist on type '[string, number]'.
336
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.
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 :
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:
Any
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 a 3rd party library. In these cases, we want to opt-out of type checking and let the
values pass through compile-time checks. To do so, we label these 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. You might expect Object to play a similar role, as it does in other languages. However, variables of type
Object only allow you to assign any value to them. You can't call arbitrary methods on them, even ones that actually exist:
Note: Avoid using Object in favor of the non-primitive object type as described in our Do's and Don'ts section.
The any type is also handy if you know some part of the type, but perhaps not all of it. For example, you may have an array but
the array has a mix of different types:
337
Basic Types
list[1] = 100;
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 --strictNullChecks is not
specified, see next section) or undefined to them:
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 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 undefined , you can use the union type string | null | undefined .
Union types are an advanced topic that we'll cover in a later chapter.
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 returns; Variables also acquire the type never
The never type is a subtype of, and assignable to, every type; however, no type is a subtype of, or assignable to, never (except
never itself). Even any isn't assignable to never .
338
Basic Types
Object
object is a type that represents the non-primitive type, i.e. anything that is not number , string , boolean , bigint ,
symbol , null , or undefined .
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
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 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.
339
Basic Types
340
Variable Declarations
Variable Declarations
let and const are two relatively new types of 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. const is an
augmentation of let in that it prevents re-assignment to a variable.
With TypeScript being a superset 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 value 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;
return b;
function g() {
return a;
341
Variable Declarations
}
}
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 later 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, but the inner for -loop will accidentally 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.
For those unfamiliar, setTimeout will try to execute a function after a certain number of milliseconds (though waiting for
anything else to stop running).
342
Variable Declarations
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 certainly 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 setTimeout 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 milliseconds, but only
after the for loop has stopped executing; By the time the for loop has stopped executing, 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:
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 introduced. 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
343
Variable Declarations
When a variable is declared using let , it uses what some call lexical-scoping or block-scoping. Unlike variables 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 limited to the
containing if statement's block.
try {
throw "oh no!";
}
catch (e) {
console.log("Oh well.");
}
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.
344
Variable Declarations
With var declarations, we mentioned that it didn't matter how many times you declared your variables; you just got one.
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() {
let x = 100;
var x = 100; // error: can't have both declarations of 'x'
}
That's not to say that 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 preventing certain bugs. For instance, imagine
we had written our earlier sumMatrix function using let variables.
return sum;
}
345
Variable Declarations
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";
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 introducing 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.
346
Variable Declarations
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.
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 codebase 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 article 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:
347
Variable Declarations
console.log(second); // outputs 2
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:
348
Variable Declarations
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:
Default values
Default values let you specify a default value in case a property is undefined:
349
Variable Declarations
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 later 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 destructuring 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 destructuring expressions small and simple. You can always write the
assignments that destructuring would generate yourself.
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 second . They are not
changed by the spread.
350
Variable Declarations
Now search is { food: "rich", price: "$$", ambiance: "noisy" } . Object spreading is more complex than array
spreading. Like array spreading, it proceeds from left-to-right, but the result is still an object. 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 feature is expected in
future versions of the language.
351
Interfaces
Introduction
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 requires that the object
passed in has a property called label of type string . Notice that our object actually 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 example. 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 requirements 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 order, only that the
properties the interface requires are present and have the required type.
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.
352
Interfaces
interface SquareConfig {
color?: string;
width?: number;
}
Interfaces with optional properties are written similar to other interfaces, with each optional property denoted 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:
interface SquareConfig {
color?: string;
width?: number;
}
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.
353
Interfaces
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 .
However, combining the two naively would allow an error to sneak in. For example, taking our last example using
createSquare :
interface SquareConfig {
color?: string;
width?: number;
}
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:
// error: Object literal may only specify known properties, but 'colour' does not exist in type 'SquareConfig'.
Did you mean to write 'color'?
let mySquare = createSquare({ colour: "red", width: 100 });
354
Interfaces
Getting around these checks is actually really simple. The easiest method is to just use a type assertion:
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.
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:
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 techniques 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 requires 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.
355
Interfaces
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:
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 infer 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 ).
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.
// error: Type '(src: string, sub: string) => string' is not assignable to type 'SearchFunc'.
// Type 'string' is not assignable to type 'boolean'.
mySearch = function(src, sub) {
let result = src.search(sub);
return "string";
};
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. Let's take an example:
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 .
356
Interfaces
There are two types of supported index signatures: string and number. It is possible to support both types of indexers, but the type
returned from a numeric indexer must be a subtype of the type returned from the string indexer. This is because when indexing
with a number , JavaScript will actually convert that to a string before indexing 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.
class Animal {
name: string;
}
class Dog extends Animal {
breed: 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:
interface NumberDictionary {
[index: string]: number;
length: number; // ok, length is a number
name: string; // error, the type of 'name' is not a subtype of the indexer
}
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:
interface ReadonlyStringArray {
readonly [index: number]: string;
}
let myArray: ReadonlyStringArray = ["Alice", "Bob"];
myArray[2] = "Mallory"; // error!
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;
357
Interfaces
You can also describe methods in an interface that are implemented in the class, as we do with setTime in the below example:
interface ClockInterface {
currentTime: Date;
setTime(d: Date): void;
}
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.
interface ClockConstructor {
new (hour: number, minute: number);
}
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 interfaces,
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;
}
358
Interfaces
tick() {
console.log("beep beep");
}
}
class AnalogClock implements ClockInterface {
constructor(h: number, m: number) { }
tick() {
console.log("tick tock");
}
}
Because createClock 's first parameter is of type ClockConstructor , in createClock(AnalogClock, 7, 32) , it checks that
AnalogClock has the correct constructor signature.
interface ClockConstructor {
new (hour: number, minute: number);
}
interface ClockInterface {
tick();
}
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;
}
interface PenStroke {
penWidth: number;
}
359
Interfaces
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 combination 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:
class Control {
private state: any;
}
360
Interfaces
select() { }
}
class Location {
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 implement SelectableControl . This is
because only descendants of Control will have a state private member 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 select method. The Button and TextBox
classes are subtypes of SelectableControl (because they both inherit from Control and have a select method), but the
Image and Location classes are not.
361
Classes
Introduction
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 will be able to
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.`);
}
}
362
Classes
dog.bark();
dog.move(10);
dog.bark();
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 keywords used to create two
new subclasses of Animal : Horse and Snake .
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.
363
Classes
Public by default
In our examples, we've been able to freely access the members that we declared throughout our programs. If you're familiar with
classes in other languages, you may have noticed in the above examples we haven't had to use the word public to accomplish
this; for instance, C# requires that each member be explicitly labeled public to be visible. In TypeScript, each member is
public by default.
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;
public constructor(theName: string) { this.name = theName; }
public move(distanceInMeters: number) {
console.log(`${this.name} moved ${distanceInMeters}m.`);
}
}
Understanding private
When a member is marked private , it cannot be accessed from outside of its containing class. For example:
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
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 differently. 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:
class Animal {
private name: string;
constructor(theName: string) { this.name = theName; }
}
class Employee {
private name: string;
constructor(theName: string) { this.name = theName; }
}
364
Classes
animal = rhino;
animal = employee; // Error: 'Animal' and 'Employee' are not compatible
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 private 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,
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
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,
class Person {
protected name: string;
protected constructor(theName: string) { this.name = theName; }
}
public getElevatorPitch() {
return `Hello, my name is ${this.name} and I work in ${this.department}.`;
}
365
Classes
Readonly modifier
You can make properties readonly by using the readonly keyword. Readonly properties must be initialized at their declaration
or in the constructor.
class Octopus {
readonly name: string;
readonly numberOfLegs: number = 8;
constructor (theName: string) {
this.name = theName;
}
}
let dad = new Octopus("Man with the 8 strong legs");
dad.name = "Man with the 3-piece suit"; // error! name is readonly.
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 parameter on the constructor to
create and initialize the name member. We've consolidated the declarations and assignment into one location.
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.
class Employee {
fullName: string;
}
366
Classes
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 something went wrong.
To preserve existing functionality, we also add a simple getter that retrieves fullName unmodified.
class Employee {
private _fullName: string;
this._fullName = newName;
}
}
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 object 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 prepending this. in front of instance accesses, here we prepend Grid. in front of static accesses.
class Grid {
static origin = {x: 0, y: 0};
calculateDistanceFromOrigin(point: {x: number; y: number;}) {
let xDist = (point.x - Grid.origin.x);
let yDist = (point.y - Grid.origin.y);
return Math.sqrt(xDist * xDist + yDist * yDist) / this.scale;
}
constructor (public scale: number) { }
}
367
Classes
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 abstract keyword is used to define abstract
classes as well as abstract methods within an abstract class.
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);
}
constructor() {
super("Accounting and Auditing"); // constructors in derived classes must call super()
}
printMeeting(): void {
console.log("The Accounting Department meets each Monday at 10am.");
}
generateReports(): void {
console.log("Generating accounting reports...");
}
}
368
Classes
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;
}
}
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:
let greeter;
greeter = new Greeter("world");
console.log(greeter.greet());
Here, let Greeter is going to be assigned the constructor function. When we call new and run this function, 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.
class Greeter {
static standardGreeting = "Hello, there";
greeting: string;
greet() {
if (this.greeting) {
return "Hello, " + this.greeting;
}
else {
return Greeter.standardGreeting;
}
}
}
369
Classes
In this example, greeter1 works similarly to before. We instantiate the Greeter class, and use this object. 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.
class Point {
x: number;
y: number;
}
370
Functions
Introduction
Functions are the fundamental building block of any application in JavaScript. They're how you build up layers of abstraction,
mimicking classes, information hiding, and modules. In TypeScript, while there are classes, 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.
// 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.
let z = 100;
function addToZ(x, y) {
return x + y + z;
}
Function Types
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.
371
Functions
Now that we've typed the function, let's write the full type of the function out by looking at each piece of the function type.
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 instead 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 a fat arrow ( => ) between 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 reflected in the type. In
effect, captured variables are part of the "hidden state" of any function and do not make up its API.
This is called "contextual typing", a form of type inference. This helps cut down on the amount of effort to keep your program
typed.
372
Functions
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 parameters we want to be optional. For example, let's say
we want the last name parameter from above to be optional:
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" .
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 lastName 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 parameters. If a default-
initialized parameter comes before a required parameter, users need to explicitly pass undefined to get the default initialized
value. For example, we could write our last example with only a default initializer on firstName :
373
Functions
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.
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.
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);
374
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 function 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 capture the this where the function is created rather than
where it is invoked:
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 easier to reuse:
interface Card {
suit: string;
card: number;
}
interface Deck {
375
Functions
suits: string[];
cards: number[];
createCardPicker(this: Deck): () => Card;
}
let deck: Deck = {
suits: ["hearts", "spades", "clubs", "diamonds"],
cards: Array(52),
// NOTE: The function now explicitly specifies that its callee must be of type Deck
createCardPicker: function(this: Deck) {
return () => {
let pickedCard = Math.floor(Math.random() * 52);
let pickedSuit = Math.floor(pickedCard / 13);
Now TypeScript knows that createCardPicker expects to be called on a Deck object. That means that this is of type Deck
parameters to prevent errors with callbacks too. First, the library author needs to annotate the callback type with this :
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 :
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!
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 :
class Handler {
info: string;
onClickGood(this: void, e: Event) {
// can't use `this` here because it's of type void!
console.log('clicked!');
}
}
let h = new Handler();
uiElement.addClickListener(h.onClickGood);
376
Functions
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:
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.
Overloads
JavaScript is inherently a very dynamic language. It's not uncommon for a single JavaScript function to return different types of
objects based on the shape of the arguments passed in.
let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
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 pickCard accepts and what it returns.
377
Functions
let myDeck = [{ suit: "diamonds", card: 2 }, { suit: "spades", card: 10 }, { suit: "hearts", card: 4 }];
let pickedCard1 = myDeck[pickCard(myDeck)];
alert("card: " + pickedCard1.card + " of " + pickedCard1.suit);
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 reason, 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 overloads: one that takes an
object and one that takes a number. Calling pickCard with any other parameter types would cause an error.
378
Generics
Introduction
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 single 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 denote 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 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 using 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:
379
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:
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.
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 parameter 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.
380
Generics
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:
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;
}
381
Generics
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 parameter 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.
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
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.
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 compiler could not prove that every type had a .length property, so it
warns us that we can't make this assumption.
382
Generics
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:
interface Lengthwise {
length: number;
}
Because the generic function is now constrained, it will no longer work over any and all types:
Instead, we need to pass in values whose type has all the required properties:
let x = { a: 1, b: 2, c: 3, d: 4 };
A more advanced example uses the prototype property to infer and constrain relationships between the constructor function and
the instance side of class types.
383
Generics
class BeeKeeper {
hasMask: boolean;
}
class ZooKeeper {
nametag: string;
}
class Animal {
numLegs: number;
}
createInstance(Lion).keeper.nametag; // typechecks!
createInstance(Bee).keeper.hasMask; // typechecks!
384
Enums
Enums
Enums allow us 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 languages. 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-incremented 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 Response {
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 constants or other constant enum members. In other
words, the following isn't allowed:
enum E {
A = getSomeValue(),
B, // Error! Enum member must have initializer.
}
385
Enums
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 "serialize" 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 }
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 subset of TypeScript
expressions that can be fully evaluated at compile time. An expression is a constant enum expression if it is:
386
Enums
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 to play.
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:
enum ShapeKind {
Circle,
Square,
}
interface Circle {
kind: ShapeKind.Circle;
radius: number;
}
interface Square {
kind: ShapeKind.Square;
sideLength: number;
}
let c: Circle = {
kind: ShapeKind.Square, // Error! Type 'ShapeKind.Square' is not assignable to type 'ShapeKind.Circle'.
radius: 100,
}
The other change is that enum types themselves effectively become a union of each enum member. While we haven't discussed
union types yet, all that you need to know is that 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 silly bugs where we might be comparing
values incorrectly. For example:
enum E {
Foo,
Bar,
387
Enums
function f(x: E) {
if (x !== E.Foo || x !== E.Bar) {
// ~~~~~~~~~~~
// Error! This condition will always return 'true' since the types 'E.Foo' and 'E.Bar' have no overlap.
}
}
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
}
enum LogLevel {
ERROR, WARN, INFO, DEBUG
}
/**
* This is equivalent to:
* type LogLevelStrings = 'ERROR' | 'WARN' | 'INFO' | 'DEBUG';
*/
type LogLevelStrings = keyof typeof LogLevel;
Reverse mappings
388
Enums
In addition to creating an object with property names for members, numeric enums members also get a reverse mapping from
enum values to enum names. For example, in this example:
enum Enum {
A
}
let a = Enum.A;
let nameOfA = Enum[a]; // "A"
TypeScript might compile this down to something like the the following JavaScript:
var Enum;
(function (Enum) {
Enum[Enum["A"] = 0] = "A";
})(Enum || (Enum = {}));
var a = Enum.A;
var 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 removed during compilation.
Const enum members are inlined at use sites. This is possible since const enums cannot have computed members.
Ambient enums
Ambient enums are used to describe the shape of already existing enum types.
389
Enums
A = 1,
B,
C = 2
}
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 constant. In contrast, an ambient (and non-
const) enum member that does not have initializer is always considered computed.
390
Type Inference
Introduction
In this section, we will cover type inference in TypeScript. Namely, we'll discuss where and how types are inferred.
Basics
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 nuances 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:
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 explicitly provide the type when no one type is
a super type of all other candidates:
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:
window.onmousedown = function(mouseEvent) {
391
Type Inference
console.log(mouseEvent.button); //<- OK
console.log(mouseEvent.kangaroo); //<- Error!
};
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.
Typescript is smart enough to infer the type of things in other contexts as well:
window.onscroll = function(uiEvent) {
console.log(uiEvent.button); //<- Error!
}
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 button property, and so Typescript will throw an
error.
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):
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:
In this example, best common type has a set of four candidates: Animal , Rhino , Elephant , and Snake . Of these, Animal
392
Type Compatibility
Introduction
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 Named {
name: string;
}
class Person {
name: string;
}
let p: Named;
// OK, because of structural typing
p = new Person();
In nominally-typed languages like C# or Java, the equivalent code would be an error because the Person class does not
explicitly describe itself as being an implementer of the Named 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 natural to represent the kinds of relationships
found in JavaScript libraries with a structural type system instead 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 behavior 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:
interface Named {
name: string;
}
let x: Named;
// y's inferred type is { name: string; location: string; }
let y = { name: "Alice", location: "Seattle" };
x = y;
To check whether y can be assigned to x , the compiler checks each property of x to find a corresponding compatible
property in y . In this case, y 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:
393
Type Compatibility
Note that y has an extra location property, but this does not create an error. Only members of the target type ( Named 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 corresponding
parameter in y with a compatible type. Note that the names of the parameters are not considered, 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 index, 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:
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.
394
Type Compatibility
// 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 .
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,
395
Type Compatibility
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 consumed 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 differentiating way. Changing
this example by adding a member to Empty<T> shows how this works:
interface NotEmpty<T> {
data: T;
}
let x: NotEmpty<number>;
396
Type Compatibility
let y: NotEmpty<string>;
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.
397
Advanced Types
Table of contents
Intersection Types
Union Types
Nullable types
Type Aliases
Discriminated Unions
Exhaustiveness checking
Index types
Mapped types
Conditional Types
Intersection Types
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 Person and Serializable and Loggable .
That means an object of this type will have all members of all three types.
You will mostly see intersection types used for mixins and other concepts that don't fit in the classic object-oriented mold. (There
are a lot of these in JavaScript!) Here's a simple example that shows how to create a mixin:
398
Advanced Types
class Person {
constructor(public name: string) { }
}
interface Loggable {
log(name: string): void;
}
Union Types
Union types are closely related to intersection types, but they are used very differently. Occasionally, you'll run into a library that
expects a parameter to be either a number or a string . For instance, 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 '${padding}'.`);
}
The problem with padLeft 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.
let indentedString = padLeft("Hello world", true); // passes at compile time, fails at runtime.
399
Advanced Types
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 concise. 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:
/**
* 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) {
// ...
}
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 .
If we have a value that has a union type, we can only access members that are common to all types in the union.
interface Bird {
fly();
layEggs();
}
interface Fish {
swim();
layEggs();
}
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.
400
Advanced Types
else if (pet.fly) {
pet.fly();
}
To get the same code working, we'll need to use a type assertion:
if ((pet as Fish).swim) {
(pet as Fish).swim();
} else if ((pet as Bird).fly) {
(pet as Bird).fly();
}
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.
pet is Fish is our type predicate in this example. A predicate takes the form parameterName is Type , where parameterName
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 .
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 .
401
Advanced Types
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.
These typeof type guards are recognized in two different forms: typeof v === "typename" and typeof v !== "typename" ,
where "typename" must be "number" , "string" , "boolean" , or "symbol" . 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
string-padder example from earlier:
interface Padder {
402
Advanced Types
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
in that order.
Nullable types
TypeScript has two special types, null and undefined , that have the values null and undefined respectively. 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:
let s = "foo";
s = null; // error, 'null' is not assignable to 'string'
let sn: string | null = "bar";
sn = null; // ok
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.
403
Advanced Types
class C {
a: number;
b?: number;
}
let c = new C();
c.a = 12;
c.a = undefined; // error, 'undefined' is not assignable to 'number'
c.b = 13;
c.b = undefined; // ok
c.b = null; // error, 'null' is not assignable to 'number | undefined'
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 :
404
Advanced Types
The example uses a nested function here because the compiler can't eliminate nulls inside a nested function (except immediately-
invoked function expressions). That's because it can't track all calls to the nested function, especially if you return it from the outer
function. Without knowing where the function is called, it can't know what the type of name will be at the time the body
executes.
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.
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;
}
405
Advanced Types
However, it's not possible for a type alias to appear anywhere else on the right side of the declaration:
One difference is that interfaces create a new name that is used everywhere. Type aliases don't create a new name — for instance,
error messages won't use the alias name. In the code below, hovering over interfaced in an editor will show that it returns an
Interface , but will show that aliased returns object literal type.
In older versions of TypeScript, type aliases couldn't be extended or implemented from (nor could they extend/implement other
types). As of version 2.7, type aliases can be extended by creating a new intersection type e.g. type Cat = Animal & { purrs:
true } .
Because an ideal property of software is being open to extension, you should always use an interface over a type alias if 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.
You can pass any of the three allowed strings, but any other string will give the error
Argument of type '"uneasy"' is not assignable to parameter of type '"ease-in" | "ease-out" | "ease-in-out"'
406
Advanced Types
String literal types can be used in the same way to distinguish overloads:
function rollDice(): 1 | 2 | 3 | 4 | 5 | 6 {
// ...
}
These are seldom written explicitly, but they can be useful when narrowing issues and can catch bugs:
In other words, x must be 1 when it gets compared to 2 , meaning that the above check is making an invalid comparison.
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.
Discriminated Unions
You can combine singleton types, union types, type guards, and type aliases to build an advanced pattern called discriminated
unions, also known as tagged unions or algebraic data types. Discriminated unions are useful in functional programming. Some
languages automatically discriminate unions for you; TypeScript instead builds on JavaScript patterns as they exist today. There
are three ingredients:
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
407
Advanced Types
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
First we declare the interfaces we will union. Each interface has a kind property with a different string literal type. The kind
property is called the discriminant or tag. The other properties are specific to each interface. Notice that the interfaces are
currently unrelated. Let's put them into a union:
Exhaustiveness checking
We would like the compiler to tell us when we don't cover all variants of the discriminated union. For example, if we add
Triangle to Shape , we need to update area as well:
There are two ways to do this. The first is to turn on --strictNullChecks and specify a return type:
Because the switch is no longer exhaustive, TypeScript is aware that the function could sometimes return undefined . If you
have an explicit return type number , then you will get an error that the return type is actually number | 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:
408
Advanced 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 requires you to define an extra function, but it's
much more obvious when you forget it.
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.
409
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;
}
let taxi: Car = {
manufacturer: 'Toyota',
model: 'Camry',
year: 2014
};
The compiler checks that manufacturer and model are actually properties on Car . The example introduces 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:
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
person['name'] has the type Person['name'] — which in our example 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 .
410
Advanced Types
In getProperty , o: T and propertyName: K , so that means o[propertyName]: T[K] . Once you return the T[K] result, the
compiler will instantiate the actual type of the key, so the return type of getProperty will vary according to which property you
request.
interface Dictionary<T> {
[key: string]: T;
}
let keys: keyof Dictionary<number>; // string | number
let value: Dictionary<number>['foo']; // number
If you have a type with a number index signature, keyof T will just be number .
interface Dictionary<T> {
[key: number]: T;
}
let keys: keyof Dictionary<number>; // number
let value: Dictionary<number>['foo']; // Error, Property 'foo' does not exist on type 'Dictionary<number>'.
let value: Dictionary<number>[42]; // number
Mapped types
A common task is to take an existing type and make each of its properties optional:
interface PersonPartial {
name?: string;
age?: number;
}
interface PersonReadonly {
readonly name: string;
readonly age: number;
}
411
Advanced Types
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 of a type readonly or optional. Here are a couple of examples:
type Readonly<T> = {
readonly [P in keyof T]: T[P];
}
type Partial<T> = {
[P in keyof T]?: T[P];
}
Note that this syntax describes a type rather than a member. If you want to add members, you can use an intersection type:
// Use this:
type PartialWithNewMember<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 boolean , 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:
412
Advanced Types
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 homomorphic, 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.
type Proxy<T> = {
get(): T;
set(value: T): void;
}
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>;
}
function proxify<T>(o: T): Proxify<T> {
// ... wrap proxies ...
}
let proxyProps = proxify(props);
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:
Non-homomorphic types are essentially creating new properties, so they can't copy property modifiers from anywhere.
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.
413
Advanced Types
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 .
A conditional type T extends U ? X : Y is either resolved to X or Y , or deferred because the condition depends on one or
more type variables. When T or U contains type variables, whether to resolve to X or Y , or to defer, is determined by
whether or not the type system has enough information to conclude that T is always assignable to U .
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 TypeName<T> =
T extends string ? "string" :
T extends number ? "number" :
T extends boolean ? "boolean" :
T extends undefined ? "undefined" :
T extends Function ? "function" :
"object";
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);
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.
414
Advanced Types
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 .
extends U ? X : Y) .
Example
In instantiations of a distributive conditional type T extends U ? X : Y , references to T within the conditional type are
resolved to individual constituents of the union type (i.e. T refers to the individual constituents after the conditional type is
distributed over the union type). Furthermore, references to T within X have an additional type parameter constraint U (i.e.
T is considered assignable to U within X ).
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
415
Advanced Types
x = y; // Ok
y = x; // Error
let s1: string = x; // Error
let s2: string = y; // Ok
}
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 recursively. 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:
type Unpacked<T> =
T extends (infer U)[] ? U :
T extends (...args: any[]) => infer U ? U :
T extends Promise<infer U> ? U :
T;
416
Advanced Types
The following example demonstrates how multiple candidates for the same type variable in co-variant positions 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), inferences 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:
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
417
Advanced Types
return { a: 1, b: s };
}
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 better conveys the semantics of the type.
418
Symbols
Introduction
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";
}
}
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.
419
Symbols
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.
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.
420
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.
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.
Code generation
421
Iterators and Generators
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:
422
Modules
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 { ).
Introduction
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 using 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:
423
Modules
isAcceptable(s: string) {
return s.length === 5 && numberRegexp.test(s);
}
}
export { ZipCodeValidator };
export { ZipCodeValidator as mainValidator };
Re-exports
Often modules extend other modules, and partially expose some of their features. A re-export does not import 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:
424
Modules
Import the entire module into a single variable, and use it to access
the module exports
import * as validator from "./ZipCodeValidator";
let myValidator = new validator.ZipCodeValidator();
import "./my-module.js";
Default exports
Each module can optionally export a default export. Default exports are marked with the keyword default ; and there can
only be one default export per module. default exports are imported using a different 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
App.ts
Classes and function declarations can be authored directly as default exports. Default export class and function declaration names
are optional.
ZipCodeValidator.ts
Test.ts
or
425
Modules
StaticZipCodeValidator.ts
Test.ts
OneTwoThree.ts
Log.ts
console.log(num); // "123"
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, interface, namespace,
function, or enum.
When exporting a module using export = , TypeScript-specific import module = require("module") must be used to import
the module.
ZipCodeValidator.ts
Test.ts
426
Modules
// Validators to use
let validator = new zip();
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
System.register(["./mod"], function(exports_1) {
var mod_1;
var t;
427
Modules
return {
setters:[
function (mod_1_1) {
mod_1 = mod_1_1;
}],
execute: function() {
exports_1("t", t = mod_1.something + 1);
}
}
});
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 --
When compiled, each module will become a separate .js file. As with reference tags, the compiler will follow import
Validation.ts
LettersOnlyValidator.ts
ZipCodeValidator.ts
428
Modules
Test.ts
// 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 optional 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 position, 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("...")) { /* ... */ }
}
declare function require(moduleNames: string[], onLoad: (...args: any[]) => void): void;
429
Modules
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:
Now we can /// <reference> node.d.ts and then load the modules using import url = require("url"); or import * as
430
Modules
declarations.d.ts
All imports from a shorthand module will have the any type.
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
431
Modules
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 namespaces 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.
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.
MyThings.ts
432
Modules
Consumer.ts
Use the namespace import pattern if you're importing a large number of things
MyLargeModule.ts
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
433
Modules
private evaluate() {
if (this.operator) {
this.memory = this.evaluateOperator(this.operator, this.memory, 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
434
Modules
Now to extend this to add support for input with numbers in bases other than 10, let's create ProgrammerCalculator.ts
ProgrammerCalculator.ts
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
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 resolve 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
435
Modules
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 going to combine into one
Foo !)
436
Namespaces
Table of contents
Introduction
First steps
Namespacing
Namespaced Validators
Multi-file namespaces
Aliases
Ambient Namespaces
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 { ).
Introduction
↥ back to top
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
↥ back to top
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.
interface StringValidator {
isAcceptable(s: string): boolean;
}
437
Namespaces
// Validators to use
let validators: { [s: string]: StringValidator; } = {};
validators["ZIP code"] = new ZipCodeValidator();
validators["Letters only"] = new LettersOnlyValidator();
Namespacing
↥ back to top
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
↥ back to top
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
438
Namespaces
// Validators to use
let validators: { [s: string]: Validation.StringValidator; } = {};
validators["ZIP code"] = new Validation.ZipCodeValidator();
validators["Letters only"] = new Validation.LettersOnlyValidator();
As our application grows, we'll want to split the code across multiple files to make it easier to maintain.
Multi-file namespaces
↥ back to top
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 relationships between the files. Our test code is otherwise unchanged.
Validation.ts
namespace Validation {
export interface StringValidator {
isAcceptable(s: string): boolean;
}
}
LettersOnlyValidator.ts
ZipCodeValidator.ts
439
Namespaces
Test.ts
// 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 flag to compile all of the input files into a single 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
440
Namespaces
↥ back to top
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 module imports.
namespace Shapes {
export namespace Polygons {
export class Triangle { }
export class Square { }
}
}
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.
To describe the shape of libraries not written in TypeScript, we need to declare the API that the library exposes. Because most
JavaScript libraries expose only a few top-level objects, namespaces are a good way to represent them.
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
↥ back to top
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;
};
}
441
Namespaces
}
}
442
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 import 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, moduleA 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 moduleA .
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:
import "/mod";
A relative import is resolved relative to the importing file and cannot resolve to an ambient module declaration. You should use
relative imports for your own modules that are guaranteed to maintain their relative location at runtime.
A non-relative import can be resolved relative to baseUrl , or through path mapping, which we'll cover below. They can also
resolve to ambient module declarations. Use non-relative paths when importing any of your external dependencies.
Classic
This used to be TypeScript's default resolution strategy. Nowadays, this strategy is mainly present for backward 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 directory containing the
importing file, trying to locate a matching definition file.
For example:
443
Module Resolution
For example:
A non-relative import to moduleB such as import { b } from "moduleB" , in a source file /root/src/folder/A.ts , would
result in attempting the following locations for locating "moduleB" :
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.
Relative paths are fairly straightforward. As an example, let's consider a file located at /root/src/moduleA.js , which contains
the import var x = require("./moduleB"); Node.js resolves that import in the following 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 considered 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 modules in special folders
named node_modules .A node_modules folder can be on the same level as the current 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
4. /root/node_modules/moduleB.js
7. /node_modules/moduleB.js
444
Module Resolution
9. /node_modules/moduleB/index.js
You can read more about the process in Node.js documentation on loading modules from node_modules .
For example, an import statement like import { b } from "./moduleB" in /root/src/moduleA.ts would result in attempting
the following locations for locating "./moduleB" :
1. /root/src/moduleB.ts
2. /root/src/moduleB.tsx
3. /root/src/moduleB.d.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 result 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
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
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
19. /node_modules/moduleB.d.ts
22. /node_modules/moduleB/index.ts
23. /node_modules/moduleB/index.tsx
445
Module Resolution
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 expected 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 "deployed" 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 relative to their importing
files.
You can find more documentation on baseUrl in RequireJS and SystemJS documentation.
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.json 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"
}
}
}
446
Module Resolution
Please notice that "paths" are resolved relative to "baseUrl" . When setting "baseUrl" to another value 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:
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
import 'folder2/file3':
1. pattern '*' is matched and wildcard captures the whole module name
2. try first substitution in the list: '*' -> folder2/file3
447
Module Resolution
Sometimes the project sources from multiple directories at compile time are all combined to generate a single output directory.
This can be viewed as a set of source directories create a "virtual" directory.
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 were merged together in one directory.
src
└── views
└── view1.ts (imports './template1')
└── view2.ts
generated
└── templates
└── views
└── template1.ts (imports './view2')
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 attempt 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 ./#{locale}/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:
export default [
"您好吗",
"很⾼兴认识你"
];
By leveraging rootDirs we can inform the compiler of this mapping and thereby allow it to safely resolve ./#
{locale}/messages , even though the directory will never exist. For example, with the following tsconfig.json :
{
"compilerOptions": {
448
Module Resolution
"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.
Let's say we have a sample application that uses the typescript module. app.ts has an import like import * as ts from
"typescript" .
│ tsconfig.json
├───node_modules
│ └───typescript
│ └───lib
│ typescript.d.ts
└───src
app.ts
tsc --traceResolution
449
Module Resolution
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
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 regardless 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
450
Declaration Merging
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 declarations 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 dotted 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 basic level, the merge
mechanically joins the members of both declarations into a single interface with the same name.
interface Box {
height: number;
width: number;
}
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.
451
Declaration Merging
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 {
createElement(tagName: string): HTMLElement;
createElement(tagName: "canvas"): HTMLCanvasElement;
}
interface Document {
createElement(tagName: "canvas"): HTMLCanvasElement;
createElement(tagName: "div"): HTMLDivElement;
createElement(tagName: "span"): HTMLSpanElement;
createElement(tagName: string): HTMLElement;
createElement(tagName: any): Element;
}
Merging Namespaces
452
Declaration Merging
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 themselves 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) namespace. This means that after merging, merged
members that came from other declarations cannot see non-exported members.
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.
453
Declaration Merging
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 {
export let suffix = "";
export let prefix = "Hello, ";
}
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 variables. For information
on mimicking class merging, see the Mixins in TypeScript section.
454
Declaration Merging
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 ...
}
// 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 ...
}
455
Declaration Merging
declare global {
interface Array<T> {
toObservable(): Observable<T>;
}
}
Array.prototype.toObservable = function () {
// ...
}
Global augmentations have the same behavior and limits as module augmentations.
456
JSX
Table of contents
Introduction
Basic Usage
The as operator
Type Checking
Intrinsic elements
Value-based elements
Function Component
Class Component
Attribute type checking
Children Type Checking
Embedding Expressions
React integration
Factory Functions
Introduction
↥ back to top
JSX is an embeddable XML-like syntax. It is meant to be transformed into valid JavaScript, though the semantics 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 checking, and compiling JSX directly to JavaScript.
Basic usage
↥ back to top
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 transformation before use, and the output will have a .js file extension. The react-native
mode is the equivalent of preserve in that it keeps all JSX, but the output will instead have a .js file extension.
457
JSX
You can specify this mode using either the --jsx command line flag or the corresponding option in your tsconfig.json file.
*Note: You can specify the JSX factory function to use when targeting react JSX emit with --jsxFactory option
(defaults to React.createElement )
The as operator
↥ back to top
This asserts the variable bar to have the type foo . Since TypeScript also uses angle brackets for type assertions, 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
↥ back to top
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 something 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 component 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 element 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
↥ back to top
Intrinsic elements are looked up on the special interface JSX.IntrinsicElements . By default, if this interface is not specified,
then anything goes and intrinsic elements will not be type checked. However, if this interface is present, then the name of the
intrinsic element is looked up as a property on the JSX.IntrinsicElements interface. For example:
458
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
↥ back to top
<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 resolve as a Function Component, TS will then try to resolve it as a class
component. If that fails, TS will report an error.
Function Component
↥ back to top
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;
}
const Button = (prop: {value: string}, context: { color: string }) => <button>
Because a Function Component is simply a JavaScript function, function overloads may be used here as well:
459
JSX
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
↥ back to top
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 instance 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.
class MyComponent {
render() {}
}
function MyFactoryFunction() {
return {
render: () => {
}
}
}
460
JSX
The element instance type is interesting because it must be assignable to JSX.ElementClass or it will result 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 {};
}
The first step to type checking attributes is to determine the element attributes type. This is slightly different between intrinsic and
value-based elements.
For value-based elements, it is a bit more complex. It is determined by the type of a property on the element 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;
461
JSX
}
}
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.
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.
In TypeScript 2.3, TS introduced type checking of children. children is a special property in an element attributes type where
child JSXExpressions are taken to be inserted into the attributes. Similar to how TS uses JSX.ElementAttributesProperty to
determine the name of props, TS uses JSX.ElementChildrenAttribute to determine the name of children within those props.
JSX.ElementChildrenAttribute should be declared with a single property.
<div>
<h1>Hello</h1>
</div>;
462
JSX
<div>
<h1>Hello</h1>
World
</div>;
You can specify the type of children like any other attribute. This will override the default type from, eg the React typings if you
use them.
interface PropsType {
children: JSX.Element
name: string
}
// OK
<Component name="foo">
<h1>Hello World</h1>
</Component>
By default the result of a JSX expression is typed as any . You can customize the type by specifying the JSX.Element interface.
However, it is not possible to retrieve type information about the element, attributes or children of the JSX from this interface. It is
a black box.
Embedding Expressions
↥ back to top
JSX allows you to embed expressions between tags by surrounding the expressions with curly braces ( { } ).
463
JSX
var 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
var a = <div>
{["foo", "bar"].map(function (i) { return <span>{i / 2}</span>; })}
</div>
React integration
↥ back to top
To use JSX with React you should use the React typings. These typings define the JSX namespace appropriately for use with
React.
interface Props {
foo: string;
}
Factory Functions
↥ back to top
The exact factory function used by the jsx: react compiler option is configurable. It may be set using either the jsxFactory
command line option, or an inline @jsx comment pragma to set it on a per-file basis. For example, if you set jsxFactory to
createElement , <div /> will emit as createElement("div") instead of React.createElement("div") .
The comment pragma version may be used like so (in TypeScript 2.8):
emits as:
464
JSX
The factory chosen will also affect where the JSX namespace is looked up (for type checking information) before falling back to
the global one. If the factory is defined as React.createElement (the default), the compiler will check for React.JSX before
checking for a global JSX . If the factory is defined as h , it will check for h.JSX before a global JSX .
465
Decorators
Introduction
With the introduction of Classes in TypeScript and ES6, there now exist certain scenarios that require additional 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' ...
}
NOTE You can see a more detailed example of a decorator in Class Decorators, below.
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.
466
Decorators
NOTE You can see a more detailed example of a decorator factory in Method Decorators, below.
Decorator Composition
Multiple decorators can be applied to a declaration, as in the following examples:
On a single line:
@f @g x
On multiple lines:
@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 equivalent 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:
function f() {
console.log("f(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("f(): called");
}
}
function g() {
console.log("g(): evaluated");
return function (target, propertyKey: string, descriptor: PropertyDescriptor) {
console.log("g(): called");
}
}
class C {
@f()
@g()
method() {}
}
f(): evaluated
g(): evaluated
g(): called
f(): called
Decorator Evaluation
There is a well defined order to how decorators applied to various declarations inside of a class are applied:
467
Decorators
1. Parameter Decorators, followed by Method, Accessor, or Property Decorators are applied for each instance 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 constructor 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.
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.
The following is an example of a class decorator ( @sealed ) applied to the Greeter class:
@sealed
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
greet() {
return "Hello, " + this.greeting;
}
}
We can define the @sealed decorator using the following function declaration:
When @sealed is executed, it will seal both the constructor and its prototype.
@classDecorator
class Greeter {
property = "property";
hello: string;
constructor(m: string) {
this.hello = m;
}
}
console.log(new Greeter("world"));
468
Decorators
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 instance 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:
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@enumerable(false)
greet() {
return "Hello, " + this.greeting;
}
}
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 definitions. 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 order. This is because decorators apply to a
Property Descriptor, which combines both the get and set 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 instance member.
469
Decorators
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 .
The following is an example of an accessor decorator ( @configurable ) applied to a member of the Point class:
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 instance member.
2. The name of the member.
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 modify 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);
470
Decorators
}
}
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-metadata 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.
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 declaration 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 instance 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.
The following is an example of a parameter decorator ( @required ) applied to parameter of a member of the Greeter class:
class Greeter {
greeting: string;
constructor(message: string) {
this.greeting = message;
}
@validate
greet(@required name: string) {
return "Hello " + name + ", " + this.greeting;
}
}
We can then define the @required and @validate decorators using the following function declarations:
import "reflect-metadata";
471
Decorators
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 option either on the command line or in your
tsconfig.json :
Command Line:
tsconfig.json:
{
"compilerOptions": {
"target": "ES5",
"experimentalDecorators": true,
"emitDecoratorMetadata": true
}
}
472
Decorators
When enabled, as long as the reflect-metadata library has been imported, additional design-time type information will be
exposed at runtime.
import "reflect-metadata";
class Point {
x: number;
y: number;
}
class Line {
private _p0: Point;
private _p1: Point;
@validate
set p0(value: Point) { this._p0 = value; }
get p0() { return this._p0; }
@validate
set p1(value: Point) { this._p1 = value; }
get p1() { return this._p1; }
}
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 _p0: Point;
private _p1: Point;
@validate
@Reflect.metadata("design:type", Point)
set p0(value: Point) { this._p0 = value; }
get p0() { return this._p0; }
@validate
@Reflect.metadata("design:type", Point)
set p1(value: Point) { this._p1 = value; }
get p1() { return this._p1; }
}
NOTE Decorator metadata is an experimental feature and may introduce breaking changes in future releases.
473
Mixins
Table of contents
Introduction
Mixin sample
Introduction
↥ back to top
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.
Mixin sample
↥ back to top
In the code below, we show how you can model mixins in TypeScript. After the code, we'll break down how it works.
// Disposable Mixin
class Disposable {
isDisposed: boolean;
dispose() {
this.isDisposed = true;
}
// Activatable Mixin
class Activatable {
isActive: boolean;
activate() {
this.isActive = true;
}
deactivate() {
this.isActive = false;
}
}
class SmartObject {
constructor() {
setInterval(() => console.log(this.isActive + " : " + this.isDisposed), 500);
}
interact() {
this.activate();
}
}
474
Mixins
////////////////////////////////////////
// In your runtime library somewhere
////////////////////////////////////////
The code sample starts with the two classes that will act as our mixins. You can see each one is focused on a particular activity or
capability. We'll later mix these together to form a new class from both capabilities.
// Disposable Mixin
class Disposable {
isDisposed: boolean;
dispose() {
this.isDisposed = true;
}
// Activatable Mixin
class Activatable {
isActive: boolean;
activate() {
this.isActive = true;
}
deactivate() {
this.isActive = false;
}
}
Next, we'll create the class that will handle the combination of the two mixins. Let's look at this in more detail to see how it does
this:
class SmartObject {
...
}
The first thing you may notice in the above is that instead of trying to extend Disposable and Activatable in SmartObject
class, we extend them in SmartObject interface. SmartObject interface will be mixed into the SmartObject class due to the
declaration merging.
This treats the classes as interfaces, and only mixes the types behind Disposable and Activatable into the SmartObject type rather
than the implementation. This means that we'll have to provide the implementation in class. Except, that's exactly what we want to
avoid by using mixins.
475
Mixins
Lastly, we create a helper function that will do the mixing for us. This will run through the properties of each of the mixins and
copy them over to the target of the mixins, filling out the stand-in properties with their implementations.
476
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.
The /// <reference path="..." /> directive is the most common of this group. It serves as a declaration of dependency
between files.
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 output 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 specified. Before a file is added to the list, all
triple-slash references in it are processed, and their targets included. Triple-slash references are resolved in a depth first manner, in
the order they have been seen in the file.
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.
Similar to a /// <reference path="..." /> directive, which serves as a declaration of dependency, a /// <reference
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.
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 compilation 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.
477
Triple-Slash Directives
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.
This directive allows a file to explicitly include an existing built-in lib file.
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 marks a file as a default library. You will see this comment at the top of lib.d.ts and its different variants.
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"/> .
By default AMD modules are generated anonymous. This can lead to problems when other tools are used to process the resulting
modules, such as bundlers (e.g. r.js ).
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
478
Triple-Slash Directives
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:
479
Type Checking and Javascript Files
TypeScript 2.3 and later support type-checking and reporting errors in .js files with --checkJs .
You can skip checking some files by adding a // @ts-nocheck comment to them; conversely, you can choose to check only a
few .js files by adding a // @ts-check comment to them without setting --checkJs . You can also ignore errors on specific
lines by adding // @ts-ignore on the preceding line. Note that if you have a tsconfig.json , JS checking will respect strict
flags like noImplicitAny , strictNullChecks , etc. However, because of the relative looseness of JS checking, combining strict
flags with it may be surprising.
Here are some notable differences on how checking works in .js files compared to .ts files:
JSDoc annotations adorning a declaration will be used to set the type of that declaration. For example:
x = 0; // OK
x = false; // Error: boolean is not assignable to number
You can find the full list of supported JSDoc patterns below.
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 assignments. Properties defined in the constructor are always assumed to
exist, whereas ones defined just in methods, getters, or setters are considered optional.
class C {
constructor() {
this.constructorOnly = 0
this.constructorUnknown = undefined
}
method() {
this.constructorOnly = false // error, constructorOnly is a number
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
initialised later:
class C {
480
Type Checking and Javascript Files
constructor() {
/** @type {number | undefined} */
this.prop = undefined;
/** @type {number | undefined} */
this.count;
}
}
function C() {
this.constructorOnly = 0
this.constructorUnknown = undefined
}
C.prototype.method = function() {
this.constructorOnly = false // error
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
481
Type Checking and Javascript Files
}
Outer.Inner = function() {
this.yy = 2
}
var ns = {}
ns.C = class {
}
ns.func = function() {
}
// 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:
482
Type Checking and Javascript Files
j = 2;
this.l = [];
}
var foo = new Foo();
foo.l.push(foo.i);
foo.l.push("end");
It is important to note that it is an error to call a function with too many arguments.
For instance:
function bar(a, b) {
console.log(a + " " + b);
}
JSDoc annotated functions are excluded from this rule. Use JSDoc optional parameter syntax to express optionality. e.g.:
/**
* @param {string} [somebody] - Somebody's name.
*/
function sayHello(somebody) {
if (!somebody) {
somebody = 'John Doe';
}
console.log('Hello ' + somebody);
}
sayHello();
A function whose body has a reference to the arguments reference is implicitly considered to have a var-arg parameter (i.e.
(...arg: any[]) => any ). Use JSDoc var-arg syntax to specify the type of the arguments.
Since there is no natural syntax for specifying generic type parameters in Javascript, an unspecified type parameter defaults to
any .
483
Type Checking and Javascript Files
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:
/** @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 default to any . For example:
p; // Promise<any>;
Supported JSDoc
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.
484
Type Checking and Javascript Files
@type
@callback
@template
The meaning is usually the same, or a superset, of the meaning of the tag given at usejsdoc.org. The code below describes the
differences and gives some example usage of each tag.
@type
You can use the "@type" tag and reference a type name (either primitive, defined in a TypeScript declaration, or in a JSDoc
"@typedef" tag). You can use any Typescript type, and most JSDoc 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;
/**
* @type {string | boolean}
*/
var sb;
485
Type Checking and Javascript Files
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 standard JSDoc syntax or
Typescript syntax.
/**
* 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 Closure syntax:
/**
* @type {*} - can be 'any' type
*/
var star;
/**
* @type {?} - unknown type (same as 'any')
*/
var question;
Casts
Typescript borrows cast syntax from 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)
486
Type Checking and Javascript Files
Import types
You can also import declarations from other files using import types. This syntax is Typescript-specific and differs from the
JSDoc standard:
/**
* @param p { import("./a").Pet }
*/
function walk(p) {
console.log(`Walking ${p.name}...`);
}
/**
* @typedef { import("./a").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:
/**
* @type {typeof import("./a").x }
*/
var x = require("./a").x;
@param uses the same type syntax as @type , but adds a parameter name. The parameter may also be declared optional by
surrounding the name with square brackets:
/**
* @return {PromiseLike<string>}
*/
function ps(){}
/**
* @returns {{ a: string, b: number }} - May use '@returns' as well as '@return'
*/
487
Type Checking and Javascript Files
function ab(){}
@typedef may be used to define complex types. Similar syntax works with @param .
/**
* @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
*/
/** @type {SpecialType} */
var specialTypeObject;
/**
* @typedef {object} SpecialType1 - creates a new type named 'SpecialType1'
* @property {string} prop1 - a string property of SpecialType1
* @property {number} prop2 - a number property of SpecialType1
* @property {number=} prop3 - an optional number property of SpecialType1
*/
/** @type {SpecialType1} */
var specialTypeObject1;
@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}
*/
/** @type {Predicate} */
const ok = s => !(s.length % 2);
Of course, any of these types can be declared using Typescript syntax in a single-line @typedef :
488
Type Checking and Javascript Files
@template
/**
* @template T
* @param {T} x - A generic parameter that flows through to the return type
* @return {T}
*/
function id(x){ return x }
/**
* @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) {
// ????
}
@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:
/**
* @constructor
* @param {number} data
*/
function C(data) {
this.size = 0;
this.initialize(data); // Should error, initializer expects a string
}
/**
* @param {string} s
*/
C.prototype.initialize = function (s) {
this.size = s.length
}
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. You will also get an error if you call C instead of constructing it.
Unfortunately, this means that constructor functions that are also callable cannot use @constructor .
489
Type Checking and Javascript Files
@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!
}
@extends
When Javascript classes extend a generic base class, there is nowhere to specify what the type parameter should be. The
@extends tag provides a place for that type parameter:
/**
* @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 extend a class.
@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.
Note that @enum is quite different from, and much simpler than, Typescript's enum . However, unlike Typescript's enums,
@enum can have any type:
More examples
var someObj = {
/**
490
Type Checking and Javascript Files
/**
* As do docs 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 functions expressions
* @param {number} x - A multiplier
*/
let myArrow = x => x * x;
/**
* Which means it works for stateless function components in JSX too
* @param {{a: string, b: number}} test - Some param
*/
var fc = (test) => <div>{test.a.charAt(0)}</div>;
/**
* A parameter can be a class constructor, using Closure syntax.
*
* @param {{new(...args: any[]): object}} C - The class to register
*/
function registerClass(C) {}
/**
* @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();
}
function aNormalFunction() {
}
/**
* @type {aNormalFunction}
*/
var wrong;
/**
* Use 'typeof' instead:
* @type {typeof aNormalFunction}
*/
var right;
491
Type Checking and Javascript Files
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: off -- number
*/
var nullable;
Non-nullable types have no meaning and are treated just as their original type:
/**
* @type {!number}
* Just has type number
*/
var normal;
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.
492
Utility Type
Introduction
TypeScript provides several utility types to facilitate common type transformations. These utilities are available globally.
Table of contents
Partial<T>
Readonly<T>
Record<K,T>
Pick<T,K>
Omit<T,K>
Exclude<T,U>
Extract<T,U>
NonNullable<T>
Parameters<T>
ConstructorParameters<T>
ReturnType<T>
InstanceType<T>
Required<T>
ThisParameterType
OmitThisParameter
ThisType<T>
Partial<T>
Constructs a type with all properties of T 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',
};
Readonly<T>
Constructs a type with all properties of T set to readonly , meaning the properties of the constructed type cannot be reassigned.
Example
493
Utility Type
Example
interface Todo {
title: string;
}
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
Record<K,T>
Constructs a type with a set of properties K of type T . This utility can be used to map the properties of a type to another type.
Example
interface PageInfo {
title: string;
}
Pick<T,K>
Constructs a type by picking the set of properties K from T .
Example
interface Todo {
title: string;
description: string;
completed: boolean;
}
494
Utility Type
Omit<T,K>
Constructs a type by picking all properties from T and then removing K .
Example
interface Todo {
title: string;
description: string;
completed: boolean;
}
Exclude<T,U>
Example
Extract<T,U>
Constructs a type by extracting from T all properties that are assignable to U .
Example
NonNullable<T>
Constructs a type by excluding null and undefined from T .
Example
Parameters<T>
Constructs a tuple type of the types of the parameters of a function type T .
495
Utility Type
Example
ConstructorParameters<T>
The ConstructorParameters<T> type lets us extract all parameter types of a constructor function type. It produces a tuple type
with all the parameter types (or the type never if T is not a function).
Example
ReturnType<T>
Constructs a type consisting of the return type of function T .
Example
InstanceType<T>
Example
class C {
x = 0;
y = 0;
}
496
Utility Type
Required<T>
Constructs a type consisting of all properties of T set to required.
Example
interface Props {
a?: number;
b?: string;
};
ThisParameterType
Extracts the type of the this parameter of a function type, or unknown if the function type has no this parameter.
Note: This type only works correctly if --strictFunctionTypes is enabled. See #32964.
Example
OmitThisParameter
Removes the 'this' parameter from a function type.
Note: This type only works correctly if --strictFunctionTypes is enabled. See #32964.
Example
// The return type of `bind` is already using `OmitThisParameter`, this is just for demonstration.
const fiveToHex: OmitThisParameter<typeof toHex> = toHex.bind(5);
console.log(fiveToHex());
ThisType<T>
This utility does not return a transformed type. Instead, it serves as a marker for a contextual this type. Note that the --
497
Utility Type
Example
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 includes 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 recognized in the
contextual type of an object literal, the interface acts like any empty interface.
498
Declaration Files
Declaration Files
Introcutoin
Library Structures
By Example
Do's and Don'ts
Deep Dive
Templates
Global Modifying Module
Publishing
Consumption
499
Introcutoin
This guide is designed to teach you how to write a high-quality TypeScript Declaration File.
In this guide, we'll assume basic familiarity with the TypeScript language. If you haven't already, you should read the TypeScript
Handbook to familiarize yourself with basic concepts, especially types and namespaces.
Sections
The guide is broken down into the following sections.
Library Structures
The Library Structures guide helps you understand common library formats and how to write a correct declaration 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 must read
this section to properly understand how the format of the library influences the writing of the declaration file.
By Example
Many times, we are faced with writing a declaration file when we only have examples of the underlying library to guide us. The
By Example section shows many common API patterns and how to write declarations for each of them. This guide is aimed at the
TypeScript novice who may not yet be familiar with every language construct in TypeScript.
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.
Templates
In Templates you'll find a number of declaration files that serve as a useful starting point when writing a new file. Refer to the
documentation in Library Structures to figure out which template file to use.
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.
500
Introcutoin
For JavaScript library users, the Consumption section offers a few simple steps to locate and install corresponding declaration
files.
501
Library Structures
Overview
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 declaration file to match it. This guide covers how
to identify common library patterns, and how to write declaration 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 organization, one might be easier than the other.
We recommend using whichever is more comfortable to you.
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:
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 library documentation
is hard to distinguish from global library documentation. Before writing a global declaration file, make sure the library isn't
actually UMD.
function createGreeting(s) {
return "Hello, " + s;
}
or like this:
window.createGreeting = function(s) {
return "Hello, " + s;
}
502
Library Structures
Calls to define(...)
Modular Libraries
Some libraries only work in a module loader environment. For example, express only works in Node.js and must be loaded
using the CommonJS require function.
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");
import fs = require("fs");
You'll typically see modular libraries include one of these lines in their documentation:
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.
503
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, especially 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 require , 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");
504
Library Structures
Be sure to read the footnote "The Impact of ES6 on Module Call Signatures"
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.
Global-modifying Modules
505
Library Structures
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:
506
Library Structures
If your global library depends on a UMD module, use a /// <reference types directive:
Footnotes
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.
507
Library Structures
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
508
By Example
Introduction
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.
Global Variables
Global Functions
Objects with Properties
Overloaded Function
Reusable Types (Interfaces)
Reusable Types (Type Aliases)
Organizing Types
Classes
The Examples
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
Global Functions
Documentation
You can call the function greet with a string to show a greeting to the user.
Code
greet("hello, world");
Declaration
509
By Example
The global variable myLib has a function makeGreeting for creating greetings, and a property numberOfGreetings
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
When specifying a greeting, you must pass a GreetingSettings object. This object has the following properties:
510
By Example
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 Greeter instance.
Code
function getGreeting() {
return "howdy";
}
class MyGreeter extends Greeter { }
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
511
By Example
Declaration
Classes
Documentation
You can create a greeter by instantiating the Greeter object, or create a customized greeter by extending 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;
}
512
Do's and Don'ts
General Types
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.
Callback Types
/* WRONG */
function fn(x: () => any) {
x();
}
Do use the return type void for callbacks whose value will be ignored:
/* 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:
513
Do's and Don'ts
Don't use optional parameters in callbacks unless you really mean it:
/* WRONG */
interface Fetcher {
getObject(done: (data: any, 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 always legal to provide a callback that accepts fewer arguments.
/* OK */
interface Fetcher {
getObject(done: (data: any, 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;
Why: It's always legal for a callback to disregard a parameter, so there's no need for the shorter overload. 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: any): any;
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;
514
Do's and Don'ts
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.
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 unspecified 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 {
515
Do's and Don'ts
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:
516
Deep Dive
By reading this guide, you'll have the tools to write complex definition files that expose a friendly API surface. 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 definition 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 explicit, 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 reference 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.
517
Deep Dive
Given a name A , we might find up to three different meanings for A : a type, a value or a namespace. How the name is
interpreted depends on the context in which it is used. For example, in the declaration let m: A.A = A; , A is used first as a
namespace, then as a type name, then as a value. These meanings might end up referring to entirely different declarations!
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 declaration class C { }
creates two things: a type C which refers to the instance shape of the class, and a value 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
Advanced Combinations
Some kinds of declarations can be combined across multiple declarations. For example, class C { } and interface C { }
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
518
Deep Dive
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
Note that in this example, we added a value to the static side of C (its constructor function). This is because we added a value,
and the container for all values is another value (types are contained by namespaces, and namespaces are contained by other
namespaces).
class C {
}
// ... elsewhere ...
namespace C {
export interface D { }
}
let y: C.D; // OK
519
Deep Dive
In this example, there wasn't a namespace C until we wrote the namespace declaration for it. The meaning 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 realistic 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:
An important rule is that export and import declarations export or import all meanings of their targets.
520
Templates
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
521
Publishing
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 package is written in TypeScript then the first approach is favored. Use the --declaration flag to generate declaration
files. This way, your declarations and JavaScript will always be in sync.
If your package is not written in TypeScript then the second is the preferred approach.
{
"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"
}
}
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 declarations. typescript , on the other hand,
packages its declaration files, so there was no need for any additional dependencies.
Our package exposes declarations from each of those, so any user of our browserify-typescript-extension 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 .
522
Publishing
Red flags
/// <reference path="..." />
Don't use /// <reference path="..." /> in your declaration files.
Make sure to revisit the Consuming dependencies section for more information.
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
https://github.com/DefinitelyTyped/DefinitelyTyped. You can find more details in the contribution guidelines page.
523
Consumption
In TypeScript 2.0, it has become significantly easier to consume declaration files, in acquiring, using, and finding them. This page
details exactly how to do all three.
Downloading
Getting type declarations in TypeScript 2.0 and above 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 https://aka.ms/types 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.
524
Project Configuration
Project Configurations
tsconfig.json
Compiler Options
Project Reference
Compiler Options with Build Tools
Nightly Builds
525
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. A project is compiled in one
of the following ways:
Using tsconfig.json
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, or a path to a valid .json file containing the configurations.
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"
]
}
{
"compilerOptions": {
"module": "system",
"noImplicitAny": true,
"removeComments": true,
"preserveConstEnums": true,
"outFile": "../../built/local/tsc.js",
"sourceMap": true
},
526
tsconfig.json
"include": [
"src/**/*"
],
"exclude": [
"node_modules",
"**/*.spec.ts"
]
}
Details
The "compilerOptions" property can be omitted, in which case the compiler's defaults are used. See our full list of supported
Compiler Options.
The "files" property takes a list of relative or absolute file paths. The "include" and "exclude" properties take a list of
glob-like file patterns. The supported glob wildcards are:
If a segment of a glob pattern includes only * or .* , then only files with supported extensions are included (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 included by those two properties. Files in the directory specified using the "outDir"
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 , jspm_packages and <outDir> directories when not specified.
Any files that are referenced by files included via the "files" or "include" properties are also included. Similarly, if a file
B.ts is referenced by another file A.ts , then B.ts cannot be excluded unless the referencing file A.ts is also specified in
the "exclude" list.
Please note that the compiler does not include files that can be possible outputs; e.g. if the input includes index.ts , then
index.d.ts and index.js are excluded. In general, having files that differ only in extension next to each other is not
recommended.
A tsconfig.json file is permitted to be completely empty, which compiles all files included by default (as described above)
with the default compiler options.
Compiler options specified on the command line override those specified in the tsconfig.json file.
By default all visible " @types " packages are included in your compilation. Packages in node_modules/@types of any enclosing
folder are considered visible; specifically, that means packages within ./node_modules/@types/ , ../node_modules/@types/ ,
../../node_modules/@types/ , and so on.
If typeRoots is specified, only packages under typeRoots will be included. For example:
527
tsconfig.json
"compilerOptions": {
"typeRoots" : ["./typings"]
}
}
This config file will include all packages under ./typings , and no packages from ./node_modules/@types .
{
"compilerOptions": {
"types" : ["node", "lodash", "express"]
}
}
A types package is a folder with a file called index.d.ts or a folder with a package.json that has a types field.
Keep in mind that automatic inclusion is only important if you're using files with global declarations (as opposed to files declared
as modules). If you use an import "foo" statement, for instance, TypeScript may still look through node_modules &
node_modules/@types folders to find the foo package.
A tsconfig.json file can inherit configurations from another file using the extends property.
The extends is a top-level property in tsconfig.json (alongside compilerOptions , files , include , and exclude ).
extends ' value is a string containing a path to another configuration file to inherit from. The path may use Node.js style
resolution.
The configuration from the base file are loaded first, then overridden by those in the inheriting config file. If a circularity is
encountered, we report an error.
files , include and exclude from the inheriting config file overwrite those from the base config file.
All relative paths found in the configuration file will be resolved relative to the configuration file they originated in.
For example:
configs/base.json :
{
"compilerOptions": {
"noImplicitAny": true,
"strictNullChecks": true
}
}
tsconfig.json :
{
"extends": "./configs/base",
"files": [
"main.ts",
"supplemental.ts"
528
tsconfig.json
]
}
tsconfig.nostrictnull.json :
{
"extends": "./tsconfig",
"compilerOptions": {
"strictNullChecks": false
}
}
compileOnSave
Setting a top-level property compileOnSave signals to the IDE to generate all files for a given tsconfig.json upon saving.
{
"compileOnSave": true,
"compilerOptions": {
"noImplicitAny" : true
}
}
This feature is currently supported in Visual Studio 2015 with TypeScript 1.8.4 and above, and atom-typescript plugin.
Schema
Schema can be found at: http://json.schemastore.org/tsconfig
529
Compiler Options
Compiler Options
Option Type Default Description
--declaration
boolean false
Generates corresponding .d.ts
-d file.
--declarationDir string
Output directory for generated
declaration files.
530
Compiler Options
-- Disallow inconsistently-cased
boolean false
forceConsistentCasingInFileNames references to the same file.
--help
-h Print help message.
--init
Initializes a TypeScript project and
creates a tsconfig.json file.
531
Compiler Options
532
Compiler Options
533
Compiler Options
--pretty boolean
another program or Stylize errors and messages using
redirecting output to a color and context.
file
DEPRECATED. Use --
jsxFactory instead.
--reactNamespace string "React" Specifies the object invoked for
createElement and __spread
when targeting "react" JSX emit.
534
Compiler Options
DEPRECATED. Use --
skipLibCheck instead.
--skipDefaultLibCheck boolean false
Skip type checking of default library
declaration files.
535
Compiler Options
[1]
These options are experimental.
[2]
These options are only allowed in tsconfig.json , and not through command-line switches.
Related
Setting compiler options in tsconfig.json files.
Setting compiler options in MSBuild projects.
536
Project Reference
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 organize 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 references 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
/src/units.ts
/test/converter-tests.ts
/test/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
{
"compilerOptions": {
// The usual
},
"references": [
{ "path": "../src" }
]
}
537
Project Reference
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, reduce 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 composite 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.
"references": [
{ "path": "../utils", "prepend": true }
]
Prepending a project will include the project's output above the output of the current project. This works for both .js files and
.d.ts files, and source map files will also 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.
538
Project Reference
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.
We're working on a behind-the-scenes .d.ts generation process that should be able to mitigate this, but for now we recommend
informing developers that they should build after cloning.
Additionally, to preserve compatibility with existing build workflows, tsc will not automatically build dependencies unless
invoked with the --build switch. Let's learn more about --build .
You can provide tsc -b with multiple config file paths (e.g. tsc -b src test ). Just like tsc -p , specifying 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.
539
Project Reference
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.
Some teams have set up msbuild-based workflows wherein tsconfig files have the same implicit graph ordering 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 endpoints 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.
540
Project Reference
541
Compiler Options in MSBuild
Overview
Compiler options can be specified using MSBuild properties within an MSBuild project.
Example
<PropertyGroup Condition="'$(Configuration)' == 'Debug'">
<TypeScriptRemoveComments>false</TypeScriptRemoveComments>
<TypeScriptSourceMap>true</TypeScriptSourceMap>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<TypeScriptRemoveComments>true</TypeScriptRemoveComments>
<TypeScriptSourceMap>false</TypeScriptSourceMap>
</PropertyGroup>
<Import
Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\Microsoft.T
ypeScript.targets"
Condition="Exists('$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\TypeScript\M
icrosoft.TypeScript.targets')" />
Mappings
Compiler Option MSBuild Property Name Allowed Values
--charset TypeScriptCharset
--
forceConsistentCasingInFileNames TypeScriptForceConsistentCasingInFileNames boolean
542
Compiler Options in MSBuild
react , react-native ,
--jsx TypeScriptJSXEmit preserve
Comma-separated list of
--lib TypeScriptLib
strings
Automatically set to
--locale automatic
PreferredUILang value
543
Compiler Options in MSBuild
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.
544
Compiler Options in MSBuild
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.
TypeScriptCompileBlocked
If you are using a different build tool to build your project (e.g. gulp, grunt , etc.) and VS for the development 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.
545
Integrating with Build Tools
Build tools
Babel
Browserify
Duo
Grunt
Gulp
Jspm
Webpack
MSBuild
NuGet
Babel
Install
.babelrc
{
"presets": ["@babel/preset-typescript"]
}
package.json
{
"scripts": {
"build": "babel --out-file bundle.js main.ts"
},
}
Browserify
Install
546
Integrating with Build Tools
Using API
browserify()
.add("main.ts")
.plugin("tsify", { noImplicitAny: true })
.bundle()
.pipe(process.stdout);
Duo
Install
Using API
Duo(__dirname)
.entry("entry.ts")
.use(typescript())
.run(function (err, results) {
if (err) throw err;
// Write compiled result to output file
fs.writeFileSync(out, results.code);
});
Grunt
Install
547
Integrating with Build Tools
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
Basic gulpfile.js
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
Webpack
Install
548
Integrating with Build Tools
module.exports = {
entry: "./src/index.tsx",
output: {
path: '/',
filename: "bundle.js"
},
resolve: {
extensions: [".tsx", ".ts", ".js", ".json"]
},
module: {
rules: [
// all files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'
{ test: /\.tsx?$/, use: ["ts-loader"], exclude: /node_modules/ }
]
}
}
module.exports = {
entry: "./src/index.tsx",
output: {
filename: "bundle.js"
},
resolve: {
// Add '.ts' and '.tsx' as a resolvable extension.
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" }
]
}
}
Alternatives:
awesome-typescript-loader
MSBuild
Update project file to include locally installed Microsoft.TypeScript.Default.props (at the top) and
Microsoft.TypeScript.targets (at the bottom) files:
549
Integrating with Build Tools
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
More details can be found at Package Manager Dialog and using nightly builds with NuGet
550
Nightly Builds
A nightly build from the TypeScript's master branch is published by midnight PST to NPM and NuGet. 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.
The nightly build currently does not include the full plugin setup, but we are working on publishing an installer on a nightly basis
as well.
551
Nightly Builds
Also see our wiki page on using a custom language service file.
For VS 2015:
For VS 2013:
552