TypeScript 2.1
keyof
and Lookup Types
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 orkeyof
;An indexed type querykeyof T
yields the type of permitted property names forT
.Akeyof T
type is considered a subtype ofstring
.
Example
ts
interfacePerson {name:string;age:number;location:string;}typeK1 =keyofPerson;// "name" | "age" | "location"typeK2 =keyofPerson[];// "length" | "push" | "pop" | "concat" | ...typeK3 =keyof { [x:string]:Person };// string
The dual of this isindexed access types, also calledlookup types.Syntactically, they look exactly like an element access, but are written as types:
Example
ts
typeP1 =Person["name"];// stringtypeP2 =Person["name" |"age"];// string | numbertypeP3 =string["charAt"];// (pos: number) => stringtypeP4 =string[]["push"];// (...items: string[]) => numbertypeP5 =string[][0];// string
You can use this pattern with other parts of the type system to get type-safe lookups.
ts
functiongetProperty<T,KextendskeyofT>(obj:T,key:K) {returnobj[key];// Inferred type is T[K]}functionsetProperty<T,KextendskeyofT>(obj:T,key:K,value:T[K]) {obj[key] =value;}letx = {foo:10,bar:"hello!" };letfoo =getProperty(x,"foo");// numberletbar =getProperty(x,"bar");// stringletoops =getProperty(x,"wargarbl");// Error! "wargarbl" is not "foo" | "bar"setProperty(x,"foo","string");// Error!, string expected number
Mapped Types
One common task is to take an existing type and make each of its properties entirely optional.Let’s say we have aPerson
:
ts
interfacePerson {name:string;age:number;location:string;}
A partial version of it would be:
ts
interfacePartialPerson {name?:string;age?:number;location?:string;}
with Mapped types,PartialPerson
can be written as a generalized transformation on the typePerson
as:
ts
typePartial<T> = {[PinkeyofT]?:T[P];};typePartialPerson =Partial<Person>;
Mapped types are produced by taking a union of literal types, and computing a set of properties for a new object type.They’re likelist comprehensions in Python, but instead of producing new elements in a list, they produce new properties in a type.
In addition toPartial
, Mapped Types can express many useful transformations on types:
ts
// Keep types the same, but make each property to be read-only.typeReadonly<T> = {readonly [PinkeyofT]:T[P];};// Same property names, but make the value a promise instead of a concrete onetypeDeferred<T> = {[PinkeyofT]:Promise<T[P]>;};// Wrap proxies around properties of TtypeProxify<T> = {[PinkeyofT]: {get():T[P];set(v:T[P]):void };};
Partial
,Readonly
,Record
, andPick
Partial
andReadonly
, as described earlier, are very useful constructs.You can use them to describe some common JS routines like:
ts
functionassign<T>(obj:T,props:Partial<T>):void;functionfreeze<T>(obj:T):Readonly<T>;
Because of that, they are now included by default in the standard library.
We’re also including two other utility types as well:Record
andPick
.
ts
// From T pick a set of properties Kdeclarefunctionpick<T,KextendskeyofT>(obj:T, ...keys:K[]):Pick<T,K>;constnameAndAgeOnly =pick(person,"name","age");// { name: string, age: number }
ts
// For every properties K of type T, transform it to UfunctionmapObject<Kextendsstring,T,U>(obj:Record<K,T>,f: (x:T)=>U):Record<K,U>;constnames = {foo:"hello",bar:"world",baz:"bye" };constlengths =mapObject(names,s=>s.length);// { foo: number, bar: number, baz: number }
Object Spread and Rest
TypeScript 2.1 brings support forESnext Spread and Rest.
Similar to array spread, spreading an object can be handy to get a shallow copy:
ts
letcopy = { ...original };
Similarly, you can merge several different objects.In the following example,merged
will have properties fromfoo
,bar
, andbaz
.
ts
letmerged = { ...foo, ...bar, ...baz };
You can also override existing properties and add new ones:
ts
letobj = {x:1,y:"string" };varnewObj = { ...obj,z:3,y:4 };// { x: number, y: number, z: number }
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:
ts
letobj = {x:1,y:1,z:1 };let {z, ...obj1 } =obj;obj1;// {x: number, y:number};
Downlevel Async Functions
This feature was supported before TypeScript 2.1, but only when targeting ES6/ES2015.TypeScript 2.1 brings the capability to ES3 and ES5 run-times, meaning you’ll be free to take advantage of it no matter what environment you’re using.
Note: first, we need to make sure our run-time has an ECMAScript-compliant
Promise
available globally.That might involve grabbinga polyfill forPromise
, or relying on one that you might have in the run-time that you’re targeting.We also need to make sure that TypeScript knowsPromise
exists by setting yourlib
option to something like"dom", "es2015"
or"dom", "es2015.promise", "es5"
Example
tsconfig.json
{" ": {" ": ["dom","es2015.promise","es5"]}}
dramaticWelcome.ts
ts
functiondelay(milliseconds:number) {returnnewPromise<void>(resolve=> {setTimeout(resolve,milliseconds);});}asyncfunctiondramaticWelcome() {console.log("Hello");for (leti =0;i <3;i++) {awaitdelay(500);console.log(".");}console.log("World!");}dramaticWelcome();
Compiling and running the output should result in the correct behavior on an ES3/ES5 engine.
Support for external helpers library (tslib
)
TypeScript injects a handful of helper functions such as__extends
for inheritance,__assign
for spread operator in object literals and JSX elements, and__awaiter
for async functions.
Previously there were two options:
- inject helpers inevery file that needs them, or
- no helpers at all with
noEmitHelpers
.
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.
First, install thetslib
utility library:
sh
npm install tslib
Second, compile your files usingimportHelpers
:
sh
tsc --module commonjs --importHelpers a.ts
So given the following input, the resulting.js
file will include an import totslib
and use the__assign
helper from it instead of inlining it.
ts
exportconsto = {a:1,name:"o" };exportconstcopy = { ...o };
js
"use strict";vartslib_1 =require("tslib");exports.o = {a:1,name:"o" };exports.copy =tslib_1.__assign({},exports.o);
Untyped imports
TypeScript has traditionally been overly strict about how you can import modules.This was to avoid typos and prevent users from using modules incorrectly.
However, a lot of the time, you might just want to import an existing module that may not have its own.d.ts
file.Previously this was an error.Starting with TypeScript 2.1 this is now much easier.
With TypeScript 2.1, you can import a JavaScript module without needing a type declaration.A type declaration (such asdeclare module "foo" { ... }
ornode_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 undernoImplicitAny
.
Example
ts
// Succeeds if `node_modules/asdf/index.js` existsimport {x }from"asdf";
Support for--target ES2016
,--target ES2017
and--target ESNext
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 likeasync
/await
.
--target ESNext
targets latest supportedES proposed features.
Improvedany
Inference
Previously, if TypeScript couldn’t figure out the type of a variable, it would choose theany
type.
ts
letx;// implicitly 'any'lety = [];// implicitly 'any[]'letz:any;// explicitly 'any'.
With TypeScript 2.1, instead of just choosingany
, TypeScript will infer types based on what you end up assigning later on.
This is only enabled ifnoImplicitAny
is set.
Example
ts
letx;// You can still assign anything you want to 'x'.x = ()=>42;// After that last assignment, TypeScript 2.1 knows that 'x' has type '() => number'.lety =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'.// TypeScript still allows you to assign anything you want to 'x'.x ="Hello world!";// But now it also knows that 'x' is a 'string'!x.toLowerCase();
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 implicitany[]
variable.However, each subsequentx.push(value)
,x.unshift(value)
orx[n] = value
operationevolves the type of the variable in accordance with what elements are added to it.
ts
functionf1() {letx = [];x.push(5);x[1] ="hello";x.unshift(true);returnx;// (string | number | boolean)[]}functionf2() {letx =null;if (cond()) {x = [];while (cond()) {x.push("hello");}}returnx;// string[] | null}
Implicit any errors
One great benefit of this is that you’ll seeway fewer implicitany
errors when running withnoImplicitAny
.Implicitany
errors are only reported when the compiler is unable to know the type of a variable without a type annotation.
Example
ts
functionf3() {letx = [];// Error: Variable 'x' implicitly has type 'any[]' in some locations where its type cannot be determined.x.push(5);functiong() {x;// Error: Variable 'x' implicitly has an 'any[]' type.}}
Better inference for literal types
String, numeric and boolean literal types (e.g."abc"
,1
, andtrue
) were previously inferred only in the presence of an explicit type annotation.Starting with TypeScript 2.1, literal types arealways inferred forconst
variables andreadonly
properties.
The type inferred for aconst
variable orreadonly
property without a type annotation is the type of the literal initializer.The type inferred for alet
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 isstring
,number
for numeric literal types,boolean
fortrue
orfalse
and the containing enum for enum literal types.
Example
ts
constc1 =1;// Type 1constc2 =c1;// Type 1constc3 ="abc";// Type "abc"constc4 =true;// Type trueconstc5 =cond ?1 :"abc";// Type 1 | "abc"letv1 =1;// Type numberletv2 =c2;// Type numberletv3 =c3;// Type stringletv4 =c4;// Type booleanletv5 =c5;// Type number | string
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, thatconst
variable gets a widening literal type inferred.But when aconst
location has an explicit literal type annotation, theconst
variable gets a non-widening literal type.
Example
ts
constc1 ="hello";// Widening type "hello"letv1 =c1;// Type stringconstc2:"hello" ="hello";// Type "hello"letv2 =c2;// Type "hello"
Use returned values from super calls as ‘this’
In ES2015, constructors which return an object implicitly substitute the value ofthis
for any callers ofsuper()
.As a result, it is necessary to capture any potential return value ofsuper()
and replace it withthis
.This change enables working withCustom Elements, which takes advantage of this to initialize browser-allocated elements with user-written constructors.
Example
ts
classBase {x:number;constructor() {// return a new object other than `this`return {x:1};}}classDerivedextendsBase {constructor() {super();this.x =2;}}
Generates:
js
varDerived = (function(_super) {__extends(Derived,_super);functionDerived() {var_this =_super.call(this) ||this;_this.x =2;return_this;}returnDerived;})(Base);
This change entails a break in the behavior of extending built-in classes like
Error
,Array
,Map
, etc.. Please see theextending built-ins breaking change documentation for more details.
Configuration inheritance
Often a project has multiple output targets, e.g.ES5
andES2015
, debug and production orCommonJS
andSystem
;Just a few configuration options change between these two targets, and maintaining multipletsconfig.json
files can be a hassle.
TypeScript 2.1 supports inheriting configuration usingextends
, where:
extends
is a new top-level property intsconfig.json
(alongsidecompilerOptions
,files
,include
, andexclude
).- The value of
extends
must be a string containing a path to another configuration file to inherit from. - The configuration from the base file is loaded first, then overridden by those in the inheriting config file.
- Circularity between configuration files is not allowed.
files
,include
, andexclude
from the inheriting config fileoverwrite 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
:
{" ": {" ":true," ":true}}
tsconfig.json
:
{" ":"./configs/base"," ": ["main.ts","supplemental.ts"]}
tsconfig.nostrictnull.json
:
{" ":"./tsconfig"," ": {" ":false}}
New--alwaysStrict
Invoking the compiler withalwaysStrict
causes:
- Parses all the code in strict mode.
- Writes
"use strict";
directive atop every generated file.
Modules are parsed automatically in strict mode.The new flag is recommended for non-module code.
The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request ❤
Last updated: Jul 14, 2025