This page is a shorter overview of features added per version.
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.
tsfunction* foo() { if (Math.random() < 0.5) yield 100; return "Finished!" } let iter = foo(); let curr = iter.next(); if (curr.done) { // TypeScript 3.5 and prior thought this was a 'string | number'. // It should know it's 'string' since 'done' was 'true'! curr.value }
Additionally, generators just assumed the type of yield
was always any
.
tsfunction* bar() { let x: { hello(): void } = yield; x.hello(); } let iter = bar(); iter.next(); iter.next(123); // oops! runtime error!
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.
tsinterface Iterator<T, TReturn = any, TNext = undefined> { // Takes either 0 or 1 arguments - doesn't accept 'undefined' next(...args: [] | [TNext]): IteratorResult<T, TReturn>; return?(value?: TReturn): IteratorResult<T, TReturn>; throw?(e?: any): IteratorResult<T, TReturn>; }
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.
tsinterface Generator<T = unknown, TReturn = any, TNext = unknown> extends Iterator<T, TReturn, TNext> { next(...args: [] | [TNext]): IteratorResult<T, TReturn>; return(value: TReturn): IteratorResult<T, TReturn>; throw(e: any): IteratorResult<T, TReturn>; [Symbol.iterator](): Generator<T, TReturn, TNext>; }
To allow differentiation between returned values and yielded values, TypeScript 3.6 converts the IteratorResult
type to a discriminated union type:
tstype IteratorResult<T, TReturn = any> = IteratorYieldResult<T> | IteratorReturnResult<TReturn>; 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.
tsfunction* 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
.
ts/** * - 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!"; } var iter = counter(); var curr = iter.next() while (!curr.done) { console.log(curr.value); curr = iter.next(curr.value === 5) } console.log(curr.value.toUpperCase()); // prints: // // 0 // 1 // 2 // 3 // 4 // 5 // DONE!
For more details on the change, see the pull request here.
More Accurate Array Spread
In pre-ES2015 targets, the most faithful emit for constructs like for
/of
loops and array spreads can be a bit heavy.
For this reason, TypeScript uses a simpler emit by default that only supports array types, and supports iterating on other types using the --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
ts[...Array(5)]
can be rewritten as the following array literal
js[undefined, undefined, undefined, undefined, undefined]
However, TypeScript would instead transform the original code into this code:
tsArray(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 more information, see the relevant pull request.
Improved UX Around Promises
TypeScript 3.6 introduces some improvements for when Promise
s are mis-handled.
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.
tsinterface User { name: string; age: number; location: string; } declare function getUserData(): Promise<User>; declare function displayUser(user: User): void; async function f() { displayUser(getUserData()); // ~~~~~~~~~~~~~ // 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.
tsasync function getCuteAnimals() { fetch("https://reddit.com/r/aww.json") .json() // ~~~~ // Property 'json' does not exist on type 'Promise<Response>'. // // Did you forget to use 'await'? }
For more details, see the originating issue, as well as the pull requests that link back to it.
Better Unicode Support for Identifiers
TypeScript 3.6 contains better support for Unicode characters in identifiers when emitting to ES2015 and later targets.
tsconst 𝓱𝓮𝓵𝓵𝓸 = "world"; // previously disallowed, now allowed in '--target es2015'
import.meta
Support in SystemJS
TypeScript 3.6 supports transforming import.meta
to context.meta
when your module
target is set to system
.
ts// This module: console.log(import.meta.url) // gets turned into the following: System.register([], function (exports, context) { return { setters: [], execute: function () { console.log(context.meta.url); } }; });
get
and set
Accessors Are Allowed in Ambient Contexts
In previous versions of TypeScript, the language didn’t allow get
and set
accessors in ambient contexts (like in declare
-d classes, or in .d.ts
files in general).
The rationale was that accessors weren’t distinct from properties as far as writing and reading to these properties;
however, because ECMAScript’s class fields proposal may have differing behavior from in existing versions of TypeScript, we realized we needed a way to communicate this different behavior to provide appropriate errors in subclasses.
As a result, users can write getters and setters in ambient contexts in TypeScript 3.6.
tsdeclare class Foo { // Allowed in 3.6+. get x(): number; set x(val: number): void; }
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.
Ambient Classes and Functions Can Merge
In previous versions of TypeScript, it was an error to merge classes and functions under any circumstances.
Now, ambient classes and functions (classes/functions with the declare
modifier, or in .d.ts
files) can merge.
This means that now you can write the following:
tsexport declare function Point2D(x: number, y: number): Point2D; export declare class Point2D { x: number; y: number; constructor(x: number, y: number); }
instead of needing to use
tsexport interface Point2D { x: number; y: number; } export declare var Point2D: { (x: number, y: number): Point2D; new (x: number, y: number): Point2D; }
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.
For more details, see the original PR on GitHub.
APIs to Support --build
and --incremental
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.
Semicolon-Aware Code Edits
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.
For more details, see the corresponding pull request.
Smarter Auto-Import Syntax
JavaScript has a lot of different module syntaxes or conventions: the one in the ECMAScript standard, the one Node already supports (CommonJS), AMD, System.js, and more!
For the most part, TypeScript would default to auto-importing using ECMAScript module syntax, which was often inappropriate in certain TypeScript projects with different compiler settings, or in Node projects with plain JavaScript and require
calls.
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.
await
Completions on Promises
New TypeScript Playground
The TypeScript playground has received a much-needed refresh with handy new functionality! The new playground is largely a fork of Artem Tyurin’s TypeScript playground which community members have been using more and more. We owe Artem a big thanks for helping out here!
The new playground now supports many new options including:
- The
target
option (allowing users to switch out ofes5
toes3
,es2015
,esnext
, etc.) - All the strictness flags (including just
strict
) - Support for plain JavaScript files (using
allowJS
and optionallycheckJs
)
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
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!
For more details, you can see the pull requests to
The Omit
helper type
TypeScript 3.5 introduces the new Omit
helper type, which creates a new type with some properties dropped from the original.
tstype Person = { name: string; age: number; location: string; }; type QuantumPerson = Omit<Person, "location">; // 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.
Improved excess property checks in union types
In TypeScript 3.4 and earlier, certain excess properties were allowed in situations where they really shouldn’t have been.
For instance, TypeScript 3.4 permitted the incorrect name
property in the object literal even though its types don’t match between Point
and Label
.
tstype Point = { x: number; y: number; }; type Label = { name: string; }; const thing: Point | Label = { x: 0, y: 0, name: true // uh-oh! };
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.
tsconst pl: Point | Label = { x: 0, y: 0, name: "origin" // okay };
The --allowUmdGlobalAccess
flag
In TypeScript 3.5, you can now reference UMD global declarations like
export as namespace foo;
from anywhere - even modules - using the new --allowUmdGlobalAccess
flag.
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.
For more details, see the pull request on GitHub.
Smarter union type checking
In TypeScript 3.4 and prior, the following example would fail:
tstype S = { done: boolean, value: number } type T = | { done: false, value: number } | { done: true, value: number }; declare let source: S; declare let target: T; 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:
tsinterface Foo { kind: "foo"; value: string; } interface Bar { kind: "bar"; value: number; } function doSomething(x: Foo | Bar) { if (x.kind === "foo") { x.value.toLowerCase(); } } // uh-oh - luckily TypeScript errors here! doSomething({ kind: "foo", value: 123, });
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.
Higher order type inference from generic constructors
In TypeScript 3.4, we improved inference for when generic functions that return functions like so:
tsfunction compose<T, U, V>( f: (x: T) => U, g: (y: U) => V): (x: T) => V { return x => g(f(x)) }
took other generic functions as arguments, like so:
tsfunction arrayify<T>(x: T): T[] { return [x]; } type Box<U> = { value: U } function boxify<U>(y: U): Box<U> { return { value: y }; } let newFn = compose(arrayify, boxify);
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[]>
.
TypeScript 3.5 generalizes this behavior to work on constructor functions as well.
tsclass Box<T> { kind: "box"; value: T; constructor(value: T) { this.value = value; } } class Bag<U> { kind: "bag"; value: U; constructor(value: U) { this.value = value; } } function composeCtor<T, U, V>( F: new (x: T) => U, G: new (y: U) => V): (x: T) => V { return x => new G(new F(x)) } let f = composeCtor(Box, Bag); // has type '<T>(x: T) => Bag<Box<T>>' let a = f(1024); // has type 'Bag<Box<number>>'
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.
tstype ComponentClass<P> = new (props: P) => Component<P>; declare class Component<P> { props: P; constructor(props: P); } declare function myHoc<P>(C: ComponentClass<P>): ComponentClass<P>; type NestedProps<T> = { foo: number, stuff: T }; declare class GenericComponent<T> extends Component<NestedProps<T>> { } // type is 'new <T>(props: NestedProps<T>) => Component<NestedProps<T>>' const GenericComponent2 = myHoc(GenericComponent);
To learn more, check out the original pull request on GitHub.
TypeScript 3.4
Faster subsequent builds with the --incremental
flag
TypeScript 3.4 introduces a new flag called --incremental
which tells TypeScript to save information about the project graph from the last compilation.
The next time TypeScript is invoked with --incremental
, it will use that information to detect the least costly way to type-check and emit changes to your project.
json// 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.
json// 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.
Higher order type inference from generic functions
TypeScript 3.4 can now produce generic function types when inference from other generic functions produces free type variables for inferences. This means many function composition patterns now work better in 3.4.
To get more specific, let’s build up some motivation and consider the following compose
function:
tsfunction compose<A, B, C>(f: (arg: A) => B, g: (arg: B) => C): (arg: A) => C { return x => g(f(x)); }
compose
takes two other functions:
f
which takes some argument (of typeA
) and returns a value of typeB
g
which takes an argument of typeB
(the typef
returned), and returns a value of typeC
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:
tsinterface Person { name: string; age: number; } function getDisplayName(p: Person) { return p.name.toLowerCase(); } function getLength(s: string) { return s.length; } // has type '(p: Person) => number' const getDisplayNameLength = compose( getDisplayName, getLength, ); // works and returns the type 'number' getDisplayNameLength({ name: "Person McPersonface", age: 42 });
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.
tsinterface Box<T> { value: T; } function makeArray<T>(x: T): T[] { return [x]; } function makeBox<U>(value: U): Box<U> { return { value }; } // has type '(arg: {}) => Box<{}[]>' const makeBoxedArray = compose( makeArray, makeBox, ) 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.
In other words, instead of producing the type
(arg: {}) => Box<{}[]>
TypeScript 3.4 produces the type
ts<T>(arg: T) => Box<T[]>
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!
tsinterface Box<T> { value: T; } function makeArray<T>(x: T): T[] { return [x]; } function makeBox<U>(value: U): Box<U> { return { value }; } // has type '<T>(arg: T) => Box<T[]>' const makeBoxedArray = compose( makeArray, makeBox, ) // works with no problem! makeBoxedArray("hello!").value[0].toUpperCase();
For more details, you can read more at the original change.
Improvements for ReadonlyArray
and readonly
tuples
TypeScript 3.4 makes it a little bit easier to use read-only array-like types.
A new syntax for ReadonlyArray
The ReadonlyArray
type describes Array
s that can only be read from.
Any variable with a reference to a ReadonlyArray
can’t add, remove, or replace any elements of the array.
tsfunction foo(arr: ReadonlyArray<string>) { arr.slice(); // okay arr.push("hello!"); // error! }
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.
tsfunction foo(arr: readonly string[]) { arr.slice(); // okay arr.push("hello!"); // error! }
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.
tsfunction foo(pair: readonly [string, string]) { console.log(pair[0]); // okay pair[1] = "hello!"; // error }
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
>
.
readonly
mapped type modifiers and readonly
arrays
In earlier versions of TypeScript, we generalized mapped types to operate differently on array-like types.
This meant that a mapped type like Boxify
could work on arrays and tuples alike.
tsinterface Box<T> { value: T } 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.
ts// lib.d.ts type Readonly<T> = { readonly [K in keyof T]: T[K] } // How code acted *before* TypeScript 3.4 // { readonly a: string, readonly b: number } type A = Readonly<{ a: string, b: number }>; // 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.
ts// How code acts now *with* TypeScript 3.4 // { readonly a: string, readonly b: number } type A = Readonly<{ a: string, b: number }>; // readonly number[] type B = Readonly<number[]>; // readonly [string, boolean] type C = Readonly<[string, boolean]>;
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.
tstype 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.
tslet err1: readonly Set<number>; // error! let err2: readonly Array<boolean>; // error! let okay: readonly boolean[]; // works fine
You can see more details in the pull request.
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"
tostring
) - object literals get
readonly
properties - array literals become
readonly
tuples
ts// Type '"hello"' let x = "hello" as const; // Type 'readonly [10, 20]' let y = [10, 20] as const; // Type '{ readonly text: "hello" }' let z = { text: "hello" } as const;
Outside of .tsx
files, the angle bracket assertion syntax can also be used.
ts// Type '"hello"' let x = <const>"hello"; // Type 'readonly [10, 20]' let y = <const>[10, 20]; // Type '{ readonly text: "hello" }' let z = <const>{ text: "hello" };
This feature means that types that would otherwise be used just to hint immutability to the compiler can often be omitted.
ts// Works with no types referenced or declared. // We only needed a single const assertion. function getShapes() { let result = [ { kind: "circle", radius: 100, }, { kind: "square", sideLength: 50, }, ] as const; return result; } for (const shape of getShapes()) { // Narrows perfectly! if (shape.kind === "circle") { console.log("Circle radius", shape.radius); } else { console.log("Square side length", shape.sideLength); } }
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.
tsexport const Colors = { red: "RED", blue: "BLUE", green: "GREEN", } as const; // or use an 'export default' 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.
ts// Error! A 'const' assertion can only be applied to a // to a string, number, boolean, array, or object literal. let a = (Math.random() < 0.5 ? 0 : 1) as const; // 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.
tslet arr = [1, 2, 3, 4]; let foo = { name: "foo", contents: arr, } as const; foo.name = "bar"; // error! foo.contents = []; // error! foo.contents.push(5); // ...works!
For more details, you can check out the respective pull request.
Type-checking for globalThis
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.
ts// in a global file: var abc = 100; // Refers to 'abc' from above. globalThis.abc = 200;
Note that global variables declared with let
and const
don’t show up on globalThis
.
tslet answer = 42; // error! Property 'answer' does not exist on 'typeof globalThis'. globalThis.answer = 333333;
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
Improved behavior for calling union types
In prior versions of TypeScript, unions of callable types could only be invoked if they had identical parameter lists.
tstype Fruit = "apple" | "orange"; type Color = "red" | "orange"; type FruitEater = (fruit: Fruit) => number; // eats and ranks the fruit type ColorConsumer = (color: Color) => string; // consumes and describes the colors declare let f: FruitEater | ColorConsumer; // Cannot invoke an expression whose type lacks a call signature. // Type 'FruitEater | ColorConsumer' has no compatible call signatures.ts(2349) f("orange");
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
.
In TypeScript 3.3, this is no longer an error.
tstype Fruit = "apple" | "orange"; type Color = "red" | "orange"; type FruitEater = (fruit: Fruit) => number; // eats and ranks the fruit type ColorConsumer = (color: Color) => string; // consumes and describes the colors declare let f: FruitEater | ColorConsumer; f("orange"); // It works! Returns a 'number | string'. 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.
tsinterface Dog { kind: "dog" dogProp: any; } interface Cat { kind: "cat" catProp: any; } const catOrDogArray: Dog[] | Cat[] = []; 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.
tsinterface Dog { kind: "dog" dogProp: any; } interface Cat { kind: "cat" catProp: any; } const catOrDogArray: Dog[] | Cat[] = []; catOrDogArray.forEach((animal: Dog | Cat) => { if (animal.kind === "dog") { animal.dogProp; // ... } else if (animal.kind === "cat") { animal.catProp; // ... } });
Incremental file watching for composite projects in --build --watch
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.
tsfunction foo(a: number, b: string): string { return a + b; } let a = foo.apply(undefined, [10]); // error: too few argumnts let b = foo.apply(undefined, [10, 20]); // error: 2nd argument is a number let c = foo.apply(undefined, [10, "hello", 30]); // error: too many arguments let d = foo.apply(undefined, [10, "hello"]); // okay! returns a string
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.
Generic spread expressions in object literals
In TypeScript 3.2, object literals now allow generic spread expressions which now produce intersection types, similar to the Object.assign
function and JSX literals. For example:
tsfunction taggedObject<T, U extends string>(obj: T, tag: U) { return { ...obj, tag }; // T & { tag: U } } let x = taggedObject({ x: 10, y: 20 }, "point"); // { x: number, y: number } & { tag: "point" }
Property assignments and non-generic spread expressions are merged to the greatest extent possible on either side of a generic spread expression. For example:
tsfunction foo1<T>(t: T, obj1: { a: string }, obj2: { b: string }) { return { ...obj1, x: 1, ...t, ...obj2, y: 2 }; // { a: string, x: number } & T & { b: string, y: number } }
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:
tsfunction spread<T, U>(t: T, u: U) { return { ...t, ...u }; // T & U } declare let x: { a: string, b: number }; declare let y: { b: string, c: boolean }; let s1 = { ...x, ...y }; // { a: string, b: string, c: boolean } let s2 = spread(x, y); // { a: string, b: number } & { b: string, c: boolean } let b1 = s1.b; // string let b2 = s2.b; // number & string
Generic object rest variables and parameters
TypeScript 3.2 also allows destructuring a rest binding from a generic variable. This is achieved by using the predefined Pick
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.
tsfunction excludeTag<T extends { tag: string }>(obj: T) { let { tag, ...rest } = obj; return rest; // Pick<T, Exclude<keyof T, "tag">> } const taggedPoint = { x: 10, y: 20, tag: "point" }; const point = excludeTag(taggedPoint); // { x: number, y: number }
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:
tslet foo: bigint = BigInt(100); // the BigInt function let bar: bigint = 100n; // a BigInt literal // *Slaps roof of fibonacci function* // This bad boy returns ints that can get *so* big! function fibonacci(n: bigint) { let result = 1n; for (let last = 0n, i = 0n; i < n; i++) { const current = result; result += last; last = current; } return result; } fibonacci(10000n)
While you might imagine close interaction between number
and bigint
, the two are separate domains.
tsdeclare let foo: number; declare let bar: bigint; foo = bar; // error: Type 'bigint' is not assignable to type 'number'. bar = foo; // error: Type 'number' is not assignable to type 'bigint'.
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.
tsconsole.log(3.141592 * 10000n); // error console.log(3145 * 10n); // error console.log(BigInt(3145) * 10n); // okay!
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.
tsfunction whatKindOfNumberIsIt(x: number | bigint) { if (typeof x === "bigint") { console.log("'x' is a bigint!"); } else { console.log("'x' is a floating-point number"); } }
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.
Non-unit types as union discriminants
TypeScript 3.2 makes narrowing easier by relaxing rules for what it considers a discriminant property.
Common properties of unions are now considered discriminants as long as they contain some singleton type (e.g. a string literal, null
, or undefined
), and they contain no generics.
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.
tstype Result<T> = | { error: Error; data: null } | { error: null; data: T }; function unwrap<T>(result: Result<T>) { if (result.error) { // Here 'error' is non-null throw result.error; } // Now 'data' is non-null return result.data; }
tsconfig.json
inheritance via Node.js packages
TypeScript 3.2 now resolves tsconfig.json
s from node_modules
. When using a bare path for the "extends"
field in tsconfig.json
, TypeScript will dive into node_modules
packages for us.
json{ "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.
The new --showConfig
flag
tsc
, the TypeScript compiler, supports a new flag called --showConfig
.
When running tsc --showConfig
, TypeScript will calculate the effective tsconfig.json
(after calculating options inherited from the extends
field) and print that out.
This can be useful for diagnosing configuration issues in general.
Object.defineProperty
declarations in JavaScript
When writing in JavaScript files (using allowJs
), TypeScript now recognizes declarations that use Object.defineProperty
.
This means you’ll get better completions, and stronger type-checking when enabling type-checking in JavaScript files (by turning on the checkJs
option or adding a // @ts-check
comment to the top of your file).
js// @ts-check let obj = {}; Object.defineProperty(obj, "x", { value: "hello", writable: false }); 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
Mapped types on tuples and arrays
In TypeScript 3.1, mapped object types[1] over tuples and arrays now produce new tuples/arrays, rather than creating a new type where members like push()
, pop()
, and length
are converted.
For example:
tstype MapToPromise<T> = { [K in keyof T]: Promise<T[K]> }; type Coordinate = [number, number] type PromiseCoordinate = MapToPromise<Coordinate>; // [Promise<number>, Promise<number>]
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
ends up with the type [Promise<number>, Promise<number>]
.
Properties declarations on functions
TypeScript 3.1 brings the ability to define properties on function declarations and const
-declared functons, simply by assigning to properties on these functions in the same scope.
This allows us to write canonical JavaScript code without resorting to namespace
hacks.
For example:
tsfunction readImage(path: string, callback: (err: any, image: Image) => void) { // ... } readImage.sync = (path: string) => { const contents = fs.readFileSync(path); return decodeImageSync(contents); }
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).
tsexport const FooComponent = ({ name }) => ( <div>Hello! I am {name}</div> ); FooComponent.defaultProps = { name: "(anonymous)", };
[1] More specifically, homomorphic mapped types like in the above form.
Version selection with typesVersions
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:
json{ "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.
json{ "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.
json{ "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
Tuples in rest parameters and spread expressions
TypeScript 3.0 adds support to multiple new capabilities to interact with function parameter lists as tuple types. TypeScript 3.0 adds support for:
- Expansion of rest parameters with tuple types into discrete parameters.
- Expansion of spread expressions with tuple types into discrete arguments.
- Generic rest parameters and corresponding inference of tuple types.
- Optional elements in tuple types.
- Rest elements in tuple types.
With these features it becomes possible to strongly type a number of higher-order functions that transform functions and their parameter lists.
Rest parameters with tuple types
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:
tsdeclare function foo(...args: [number, string, boolean]): void;
tsdeclare function foo(args_0: number, args_1: string, args_2: boolean): void;
Spread expressions with tuple types
When a function call includes a spread expression of a tuple type as the last argument, the spread expression corresponds to a sequence of discrete arguments of the tuple element types.
Thus, the following calls are equivalent:
tsconst args: [number, string, boolean] = [42, "hello", true]; foo(42, "hello", true); foo(args[0], args[1], args[2]); foo(...args);
Generic rest parameters
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
tsdeclare function bind<T, U extends any[], V>(f: (x: T, ...args: U) => V, x: T): (...args: U) => V; declare function f3(x: number, y: string, z: boolean): void; const f2 = bind(f3, 42); // (y: string, z: boolean) => void const f1 = bind(f2, "hello"); // (z: boolean) => void const f0 = bind(f1, true); // () => void f3(42, "hello", true); f2("hello", true); f1(true); f0();
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).
Optional elements in tuple types
Tuple types now permit a ?
postfix on element types to indicate that the element is optional:
Example
tslet t: [number, string?, boolean?]; t = [42, "hello", true]; t = [42, "hello"]; t = [42];
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
.
Rest elements in tuple types
The last element of a tuple type can be a rest element of the form ...X
, where X
is an array type.
A rest element indicates that the tuple type is open-ended and may have zero or more additional elements of the array element type.
For example, [number, ...string[]]
means tuples with a number
element followed by any number of string
elements.
Example
tsfunction tuple<T extends any[]>(...args: T): T { return args; } const numbers: number[] = getArrayOfNumbers(); const t1 = tuple("foo", 1, true); // [string, number, boolean] const t2 = tuple("bar", ...numbers); // [string, ...number[]]
The type of the length
property of a tuple type with a rest element is number
.
New unknown
top type
TypeScript 3.0 introduces a new top type unknown
.
unknown
is the type-safe counterpart of any
.
Anything is assignable to unknown
, but unknown
isn’t assignable to anything but itself and any
without a type assertion or a control flow based narrowing.
Likewise, no operations are permitted on an unknown
without first asserting or narrowing to a more specific type.
Example
ts// In an intersection everything absorbs unknown type T00 = unknown & null; // null type T01 = unknown & undefined; // undefined type T02 = unknown & null & undefined; // null & undefined (which becomes never) type T03 = unknown & string; // string type T04 = unknown & string[]; // string[] type T05 = unknown & unknown; // unknown type T06 = unknown & any; // any // In a union an unknown absorbs everything type T10 = unknown | null; // unknown type T11 = unknown | undefined; // unknown type T12 = unknown | null | undefined; // unknown type T13 = unknown | string; // unknown type T14 = unknown | string[]; // unknown type T15 = unknown | unknown; // unknown type T16 = unknown | any; // any // Type variable and unknown in union and intersection type T20<T> = T & {}; // T & {} type T21<T> = T | {}; // T | {} type T22<T> = T & unknown; // T type T23<T> = T | unknown; // unknown // unknown in conditional types type T30<T> = unknown extends T ? true : false; // Deferred type T31<T> = T extends unknown ? true : false; // Deferred (so it distributes) type T32<T> = never extends T ? true : false; // true type T33<T> = T extends never ? true : false; // Deferred // keyof unknown type T40 = keyof any; // string | number | symbol type T41 = keyof unknown; // never // Only equality operators are allowed with unknown function f10(x: unknown) { x == 5; x !== 10; x >= 0; // Error x + 1; // Error x * 2; // Error -x; // Error +x; // Error } // No property accesses, element accesses, or function calls function f11(x: unknown) { x.foo; // Error x[5]; // Error x(); // Error new x(); // Error } // typeof, instanceof, and user defined type predicates declare function isFunction(x: unknown): x is Function; function f20(x: unknown) { if (typeof x === "string" || typeof x === "number") { x; // string | number } if (x instanceof Error) { x; // Error } if (isFunction(x)) { x; // Function } } // Homomorphic mapped type over unknown type T50<T> = { [P in keyof T]: number }; type T51 = T50<any>; // { [x: string]: number } type T52 = T50<unknown>; // {} // Anything is assignable to unknown function f21<T>(pAny: any, pNever: never, pT: T) { let x: unknown; x = 123; x = "hello"; x = [1, 2, 3]; x = new Error(); x = x; x = pAny; x = pNever; x = pT; } // unknown assignable only to itself and any function f22(x: unknown) { let v1: any = x; let v2: unknown = x; let v3: object = x; // Error let v4: string = x; // Error let v5: string[] = x; // Error let v6: {} = x; // Error let v7: {} | null | undefined = x; // Error } // Type parameter 'T extends unknown' not related to object function f23<T extends unknown>(x: T) { let y: object = x; // Error } // Anything but primitive assignable to { [x: string]: unknown } function f24(x: { [x: string]: unknown }) { x = {}; x = { a: 5 }; x = [1, 2, 3]; x = 123; // Error } // Locals of type unknown always considered initialized function f25() { let x: unknown; let y = x; } // Spread of unknown causes result to be unknown function f26(x: {}, y: unknown, z: any) { let o1 = { a: 42, ...x }; // { a: number } let o2 = { a: 42, ...x, ...y }; // unknown let o3 = { a: 42, ...x, ...y, ...z }; // any } // Functions with unknown return type don't need return expressions function f27(): unknown { } // Rest type cannot be created from unknown function f28(x: unknown) { let { ...a } = x; // Error } // Class properties of type unknown don't need definite assignment class C1 { a: string; // Error b: unknown; c: any; }
Support for defaultProps
in JSX
TypeScript 2.9 and earlier didn’t leverage React defaultProps
declarations inside JSX components.
Users would often have to declare properties optional and use non-null assertions inside of render
, or they’d use type-assertions to fix up the type of the component before exporting it.
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
.
tsxexport interface Props { name: string; } export class Greet extends React.Component<Props> { render() { const { name } = this.props; return <div>Hello ${name.toUpperCase()}!</div>; } static defaultProps = { name: "world"}; } // Type-checks! No type assertions needed! let el = <Greet />
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 stateless function components (SFCs) use ES2015 default initializers for SFCs:
tsxfunction Greet({ name = "world" }: Props) { return <div>Hello ${name.toUpperCase()}!</div>; }
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.
/// <reference lib="..." />
reference directives
TypeScript adds a new triple-slash-reference directive (/// <reference lib="name" />
), allowing 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 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
.
ts/// <reference lib="es2017.string" /> "foo".padStart(4);
TypeScript 2.9
Support number
and symbol
named properties with keyof
and mapped types
TypeScript 2.9 adds support for number
and symbol
named properties in index types and mapped types.
Previously, the keyof
operator and mapped types only supported string
named properties.
Changes include:
- An index type
keyof T
for some typeT
is a subtype ofstring | number | symbol
. - A mapped type
{ [P in K]: XXX }
permits anyK
assignable tostring | number | symbol
. - In a
for...in
statement for an object of a generic typeT
, the inferred type of the iteration variable was previouslykeyof T
but is nowExtract<keyof T, string>
. (In other words, the subset ofkeyof T
that includes only string-like values.)
Given an object type X
, keyof X
is resolved as follows:
- If
X
contains a string index signature,keyof X
is a union ofstring
,number
, and the literal types representing symbol-like properties, otherwise - If
X
contains a numeric index signature,keyof X
is a union ofnumber
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
tsconst 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 } type K1 = keyof Foo; // "a" | 5 | "c" | 10 | typeof e | E1.A | E2.A type K2 = Extract<keyof Foo, string>; // "a" | "c" | E2.A type K3 = Extract<keyof Foo, number>; // 5 | 10 | E1.A type K4 = Extract<keyof Foo, symbol>; // typeof e
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:
tstype Arrayish<T> = { length: number; [x: number]: T; } type ReadonlyArrayish<T> = Readonly<Arrayish<T>>; declare const map: ReadonlyArrayish<string>; let n = map.length; let x = map[123]; // Previously of type any (or an error with --noImplicitAny)
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.
tsconst enum Enum { A, B, C } const enumToStringMap = { [Enum.A]: "Name A", [Enum.B]: "Name B", [Enum.C]: "Name C" } const sym1 = Symbol(); const sym2 = Symbol(); const sym3 = Symbol(); const symbolToNumberMap = { [sym1]: 1, [sym2]: 2, [sym3]: 3 }; type KE = keyof typeof enumToStringMap; // Enum (i.e. Enum.A | Enum.B | Enum.C) type KS = keyof typeof symbolToNumberMap; // typeof sym1 | typeof sym2 | typeof sym3 function getValue<T, K extends keyof T>(obj: T, key: K): T[K] { return obj[key]; } let x1 = getValue(enumToStringMap, Enum.C); // Returns "Name C" let x2 = getValue(symbolToNumberMap, sym3); // Returns 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
tsfunction useKey<T, K extends keyof T>(o: T, k: K) { var name: string = k; // Error: keyof T is not assignable to string }
Recommendations
-
If your functions are only able to handle string named property keys, use
Extract<keyof T, string>
in the declaration:tsfunction useKey<T, K extends Extract<keyof T, string>>(o: T, k: K) { var name: string = k; // OK }
-
If your functions are open to handling all property keys, then the changes should be done down-stream:
tsfunction useKey<T, K extends keyof T>(o: T, k: K) { var name: string | number | symbol = k; }
- Otherwise use
--keyofStringsOnly
compiler option to disable the new behavior.
Generic type arguments in JSX elements
JSX elements now allow passing type arguments to generic components.
Example
tsclass GenericComponent<P> extends React.Component<P> { internalProp: P; } type Props = { a: number; b: string; }; const x = <GenericComponent<Props> a={10} b="hi"/>; // OK const y = <GenericComponent<Props> a={10} b={20} />; // Error
Generic type arguments in generic tagged templates
Tagged templates are a form of invocation introduced in ECMAScript 2015. Like call expressions, generic functions may be used in a tagged template and TypeScript will infer the type arguments utilized.
TypeScript 2.9 allows passing generic type arguments to tagged template strings.
Example
tsdeclare function styledComponent<Props>(strs: TemplateStringsArray): Component<Props>; interface MyProps { name: string; age: number; } styledComponent<MyProps> ` font-size: 1.5em; text-align: center; color: palevioletred; `; declare function tag<T>(strs: TemplateStringsArray, ...args: T[]): T; // 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:
ts// module.d.ts export declare class Pet { name: string; }
Can be used in a non-module file global-script.ts
:
ts// global-script.ts function adopt(p: import("./module").Pet) { console.log(`Adopting ${p.name}...`); }
This also works in JSDoc comments to refer to types from other modules in .js
:
js// a.js /** * @param p { import("./module").Pet } */ function walk(p) { console.log(`Walking ${p.name}...`); }
Relaxing declaration emit visiblity rules
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:
tsimport { createHash } from "crypto"; export const hash = createHash("sha256"); // ^^^^ // Exported variable 'hash' has or is using name 'Hash' from external module "crypto" but cannot be named.
With TypeScript 2.9, no errors are reported, and now the generated file looks like:
tsexport declare const hash: import("crypto").Hash;
Support for import.meta
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:
ts// node.d.ts interface ImportMeta { __dirname: string; }
And usage would be:
tsimport.meta.__dirname // Has type '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
ts// settings.json { "repo": "TypeScript", "dry": false, "debug": false }
ts// a.ts import settings from "./settings.json"; settings.debug === true; // OK settings.dry === 2; // Error: Operator '===' cannot be applied boolean and number
ts// tsconfig.json { "compilerOptions": { "module": "commonjs", "resolveJsonModule": true, "esModuleInterop": true } }
--pretty
output by default
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
.
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:
tsT 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'
andU'
that are instantiations ofT
andU
where all occurrences of type parameters are replaced withany
, ifT'
is not assignable toU'
, the conditional type is resolved toY
. Intuitively, if the most permissive instantiation ofT
is not assignable to the most permissive instantiation ofU
, we know that no instantiation will be and we can just resolve toY
. - Next, for each type variable introduced by an
infer
(more later) declaration withinU
collect a set of candidate types by inferring fromT
toU
(using the same inference algorithm as type inference for generic functions). For a giveninfer
type variableV
, if any candidates were inferred from co-variant positions, the type inferred forV
is a union of those candidates. Otherwise, if any candidates were inferred from contra-variant positions, the type inferred forV
is an intersection of those candidates. Otherwise, the type inferred forV
isnever
. - Then, given a type
T''
that is an instantiation ofT
where allinfer
type variables are replaced with the types inferred in the previous step, ifT''
is definitely assignable toU
, the conditional type is resolved toX
. 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
tstype TypeName<T> = T extends string ? "string" : T extends number ? "number" : T extends boolean ? "boolean" : T extends undefined ? "undefined" : T extends Function ? "function" : "object"; type T0 = TypeName<string>; // "string" type T1 = TypeName<"a">; // "string" type T2 = TypeName<true>; // "boolean" type T3 = TypeName<() => void>; // "function" type T4 = TypeName<string[]>; // "object"
Distributive conditional types
Conditional types in which the checked type is a naked type parameter are called distributive conditional types.
Distributive conditional types are automatically distributed over union types during instantiation.
For example, an instantiation of T extends U ? X : Y
with the type argument A | B | C
for T
is resolved as (A extends U ? X : Y) | (B extends U ? X : Y) | (C extends U ? X : Y)
.
Example
tstype T10 = TypeName<string | (() => void)>; // "string" | "function" type T12 = TypeName<string | string[] | undefined>; // "string" | "object" | "undefined" type T11 = TypeName<string[] | number[]>; // "object"
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
tstype BoxedValue<T> = { value: T }; type BoxedArray<T> = { array: T[] }; type Boxed<T> = T extends any[] ? BoxedArray<T[number]> : BoxedValue<T>; type T20 = Boxed<string>; // BoxedValue<string>; type T21 = Boxed<number[]>; // BoxedArray<number>; type T22 = Boxed<string | number[]>; // BoxedValue<string> | BoxedArray<number>;
Notice that T
has the additional constraint any[]
within the true branch of Boxed<T>
and it is therefore possible to refer to the element type of the array as T[number]
. Also, notice how the conditional type is distributed over the union type in the last example.
The distributive property of conditional types can conveniently be used to filter union types:
tstype 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 type T34 = NonNullable<string | number | undefined>; // string | number type T35 = NonNullable<string | string[] | null | undefined>; // string | string[] function f1<T>(x: T, y: NonNullable<T>) { x = y; // Ok y = x; // Error } function f2<T extends string | undefined>(x: T, y: NonNullable<T>) { 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:
tstype 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; } type T40 = FunctionPropertyNames<Part>; // "updatePart" type T41 = NonFunctionPropertyNames<Part>; // "id" | "name" | "subparts" type T42 = FunctionProperties<Part>; // { updatePart(newName: string): void } type T43 = NonFunctionProperties<Part>; // { id: number, name: string, subparts: Part[] }
Similar to union and intersection types, conditional types are not permitted to reference themselves recursively. For example the following is an error.
Example
tstype ElementType<T> = T extends any[] ? ElementType<T[number]> : T; // Error
Type inference in conditional types
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:
tstype ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;
Conditional types can be nested to form a sequence of pattern matches that are evaluated in order:
tstype Unpacked<T> = T extends (infer U)[] ? U : T extends (...args: any[]) => infer U ? U : T extends Promise<infer U> ? U : T; type T0 = Unpacked<string>; // string type T1 = Unpacked<string[]>; // string type T2 = Unpacked<() => string>; // string type T3 = Unpacked<Promise<string>>; // string type T4 = Unpacked<Promise<string>[]>; // Promise<string> type T5 = Unpacked<Unpacked<Promise<string>[]>>; // string
The following example demonstrates how multiple candidates for the same type variable in co-variant positions causes a union type to be inferred:
tstype Foo<T> = T extends { a: infer U, b: infer U } ? U : never; type T10 = Foo<{ a: string, b: string }>; // string type T11 = Foo<{ a: string, b: number }>; // string | number
Likewise, multiple candidates for the same type variable in contra-variant positions causes an intersection type to be inferred:
tstype 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.
tsdeclare function foo(x: string): number; declare function foo(x: number): string; declare function foo(x: string | number): string | number; type T30 = ReturnType<typeof foo>; // string | number
It is not possible to use infer
declarations in constraint clauses for regular type parameters:
tstype 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:
tstype AnyFunction = (...args: any[]) => any; type ReturnType<T extends AnyFunction> = T extends (...args: any[]) => infer R ? R : any;
Predefined conditional types
TypeScript 2.8 adds several predefined conditional types to lib.d.ts
:
Exclude<T, U>
— Exclude fromT
those types that are assignable toU
.Extract<T, U>
— Extract fromT
those types that are assignable toU
.NonNullable<T>
— Excludenull
andundefined
fromT
.ReturnType<T>
— Obtain the return type of a function type.InstanceType<T>
— Obtain the instance type of a constructor function type.
Example
tstype 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 type T04 = NonNullable<string | number | undefined>; // string | number type T05 = NonNullable<(() => string) | string[] | null | undefined>; // (() => string) | string[] function f1(s: string) { return { a: 1, b: s }; } class C { x = 0; y = 0; } type T10 = ReturnType<() => string>; // string type T11 = ReturnType<(s: string) => void>; // void type T12 = ReturnType<(<T>() => T)>; // {} type T13 = ReturnType<(<T extends U, U extends number[]>() => T)>; // number[] type T14 = ReturnType<typeof f1>; // { a: number, b: string } type T15 = ReturnType<any>; // any type T16 = ReturnType<never>; // any type T17 = ReturnType<string>; // Error type T18 = ReturnType<Function>; // Error type T20 = InstanceType<typeof C>; // C type T21 = InstanceType<any>; // any type T22 = InstanceType<never>; // any type T23 = InstanceType<string>; // Error type T24 = InstanceType<Function>; // Error
Note: The
Exclude
type is a proper implementation of theDiff
type suggested here. We’ve used the nameExclude
to avoid breaking existing code that defines aDiff
, plus we feel that name better conveys the semantics of the type. We did not include theOmit<T, K>
type because it is trivially written asPick<T, Exclude<keyof T, K>>
.
Improved control over mapped type modifiers
Mapped types support adding a readonly
or ?
modifier to a mapped property, but they did not provide support the ability to remove modifiers.
This matters in homomorphic mapped types which by default preserve the modifiers of the underlying type.
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
tstype MutableRequired<T> = { -readonly [P in keyof T]-?: T[P] }; // Remove readonly and ? type ReadonlyPartial<T> = { +readonly [P in keyof T]+?: T[P] }; // Add readonly and ?
A modifier with no +
or -
prefix is the same as a modifier with a +
prefix. So, the ReadonlyPartial<T>
type above corresponds to
tstype ReadonlyPartial<T> = { readonly [P in keyof T]?: T[P] }; // Add readonly and ?
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
tstype Required<T> = { [P in keyof T]-?: T[P] };
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
tstype Foo = { a?: string }; // Same as { a?: string | undefined } type Bar = Required<Foo>; // Same as { a: string }
Improved keyof
with intersection types
With TypeScript 2.8 keyof
applied to an intersection type is transformed to a union of keyof
applied to each intersection constituent.
In other words, types of the form keyof (A & B)
are transformed to be keyof A | keyof B
.
This change should address inconsistencies with inference from keyof
expressions.
Example
tstype A = { a: string }; type B = { b: string }; type T1 = keyof (A & B); // "a" | "b" type T2<T> = keyof (T & B); // keyof T | "b" type T3<U> = keyof (A & U); // "a" | keyof U type T4<T, U> = keyof (T & U); // keyof T | keyof U type T5 = T2<A>; // "a" | "b" type T6 = T3<B>; // "a" | "b" type T7 = T4<A, B>; // "a" | "b"
Better handling for namespace patterns in .js
files
TypeScript 2.8 adds support for understanding more namespace patterns in .js
files.
Empty object literals declarations on top level, just like functions and classes, are now recognized as as namespace declarations in JavaScript.
jsvar ns = {}; // recognized as a declaration for a namespace `ns` ns.constant = 1; // recognized as a declaration for var `constant`
Assignments at the top-level should behave the same way; in other words, a var
or const
declaration is not required.
jsapp = {}; // does NOT need to be `var app = {}` app.C = class { }; app.f = function() { }; app.prop = 1;
IIFEs as namespace declarations
An IIFE returning a function, class or empty object literal, is also recognized as a namespace:
jsvar 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:
jsmy = 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:
tsvar C = function (p) { this.p = p; }; C.prototype = { m() { console.log(this.p); } }; C.prototype.q = function(r) { return this.p === r; };
Nested and merged declarations
Nesting works to any level now, and merges correctly across files. Previously neither was the case.
jsvar app = window.app || {}; app.C = class { };
Per-file JSX factories
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
ts/** @jsx dom */ import { dom } from "./renderer" <h></h>
Generates:
jsvar renderer_1 = require("./renderer"); renderer_1.dom("h", null);
Locally scoped JSX namespaces
JSX type checking is driven by definitions in a JSX namespace, for instance JSX.Element
for the type of a JSX element, and JSX.IntrinsicElements
for built-in elements.
Before TypeScript 2.8 the JSX
namespace was expected to be in the global namespace, and thus only allowing one to be defined in a project.
Starting with TypeScript 2.8 the JSX
namespace will be looked under the jsxNamespace
(e.g. React
) allowing for multiple jsx factories in one compilation.
For backward compatibility the global JSX
namespace is used as a fallback if none was defined on the factory function.
Combined with the per-file @jsx
pragma, each file can have a different JSX factory.
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
ts// Lib export const SERIALIZE = Symbol("serialize-method-key"); export interface Serializable { [SERIALIZE](obj: {}): string; }
ts// consumer import { SERIALIZE, Serializable } from "lib"; class JSONSerializableItem implements Serializable { [SERIALIZE](obj: {}) { return JSON.stringify(obj); } }
This also applies to numeric and string literals.
Example
tsconst Foo = "Foo"; const Bar = "Bar"; let x = { [Foo]: 100, [Bar]: "hello", }; let a = x[Foo]; // has type 'number' let b = x[Bar]; // has type 'string'
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
ts// Works declare const Foo: unique symbol; // Error! 'Bar' isn't a constant. let Bar: unique symbol = Symbol(); // Works - refers to a unique symbol, but its identity is tied to 'Foo'. let Baz: typeof Foo = Foo; // 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
tsconst Foo = Symbol(); const Bar = Symbol(); // Error: can't compare two unique symbols. if (Foo === Bar) { // ... }
Strict Class Initialization
TypeScript 2.7 introduces a new flag called --strictPropertyInitialization
.
This flag performs checks to ensure that each instance property of a class gets initialized in the constructor body, or by a property initializer.
For example
tsclass 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).
tsclass 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.
Definite Assignment Assertions
The definite assignment assertion is a feature that allows a !
to be placed after instance property and variable declarations to relay to TypeScript that a variable is indeed assigned for all intents and purposes, even if TypeScript’s analyses cannot detect so.
For example:
tslet 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:
ts// Notice the '!' let x!: number; initialize(); // 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.
tslet 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.
Fixed Length Tuples
In TypeScript 2.6 and earlier, [number, string, string]
was considered a subtype of [number, string]
.
This was motivated by TypeScript’s structural nature; the first and second elements of a [number, string, string]
are respectively subtypes of the first and second elements of [number, string]
.
However, after examining real world usage of tuples, we noticed that most situations in which this was permitted was typically undesirable.
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
:
tsinterface NumStrTuple extends Array<number | string> { 0: number; 1: string; length: 2; // using the numeric literal type '2' }
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
.
tsinterface MinimumNumStrTuple extends Array<number | string> { 0: number; 1: string; }
Note that this does not imply tuples represent immutable arrays, but it is an implied convention.
Improved type inference for object literals
TypeScript 2.7 improves type inference for multiple object literals occurring in the same context. When multiple object literal types contribute to a union type, we now normalize the object literal types such that all properties are present in each constituent of the union type.
Consider:
tsconst 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
ts// let obj: { a: number, b: number } | // { a: string, b?: undefined } | // { a?: undefined, b?: undefined } let obj = [{ a: 1, b: 2 }, { a: "abc" }, {}][0]; obj.a; // string | number | undefined obj.b; // number | undefined
Multiple object literal type inferences for the same type parameter are similarly collapsed into a single normalized union type:
tsdeclare function f<T>(...items: T[]): T; // let obj: { a: number, b: number } | // { a: string, b?: undefined } | // { a?: undefined, b?: undefined } let obj = f({ a: 1, b: 2 }, { a: "abc" }, {}); obj.a; // string | number | undefined obj.b; // number | undefined
Improved handling of structurally identical classes and instanceof
expressions
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:
tsclass A {} class B extends A {} class C extends A {} class D extends A { c: string } class E extends D {} let x1 = !true ? new A() : new B(); // A let x2 = !true ? new B() : new C(); // B | C (previously B) let x3 = !true ? new C() : new D(); // C | D (previously C) 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) } }
Type guards inferred from in
operator
The in
operator now acts as a narrowing expression for types.
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
tsinterface A { a: number }; interface B { b: string }; function foo(x: A | B) { if ("a" in x) { return x.a; } return x.b; }
Support for import d from "cjs"
form CommonJS modules with --esModuleInterop
TypeScript 2.7 updates CommonJS/AMD/UMD module emit to synthesize namespace records based on the presence of an __esModule
indicator under --esModuleInterop
.
The change brings the generated output from TypeScript closer to that generated by Babel.
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 toconst 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 toconst d = require("foo").default
. Most of the CommonJS/AMD/UMD modules available today do not have adefault
export, making this import pattern practically unusable to import non-ES modules (i.e. CommonJS/AMD/UMD). For instanceimport fs from "fs"
orimport express from "express"
are not allowed.
Under the new --esModuleInterop
these two issues should be addressed:
- 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:
tsimport * as foo from "foo"; import b from "bar";
Will generate:
js"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
tsconst milion = 1_000_000; const phone = 555_734_2231; const bytes = 0xFF_0C_00_FF; const word = 0b1100_0011_1101_0001;
Cleaner output in --watch
mode
TypeScript’s --watch
mode now clears the screen after a re-compilation is requested.
Prettier --pretty
output
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
Strict function types
TypeScript 2.6 introduces a new strict checking flag, --strictFunctionTypes
.
The --strictFunctionTypes
switch is part of the --strict
family of switches, meaning that it defaults to on in --strict
mode.
You can opt-out by setting --strictFunctionTypes false
on your command line or in your tsconfig.json.
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
:
tsdeclare let f1: (x: Animal) => void; declare let f2: (x: Dog) => void; declare let f3: (x: Cat) => void; f1 = f2; // Error with --strictFunctionTypes f2 = f1; // Ok f2 = f3; // Error
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
tsinterface Comparer<T> { compare: (a: T, b: T) => number; } declare let animalComparer: Comparer<Animal>; declare let dogComparer: Comparer<Dog>; animalComparer = dogComparer; // Error dogComparer = animalComparer; // Ok
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.
tsinterface Comparer<T> { compare(a: T, b: T): number; } declare let animalComparer: Comparer<Animal>; declare let dogComparer: Comparer<Dog>; animalComparer = dogComparer; // Ok because of bivariance dogComparer = animalComparer; // Ok
TypeScript 2.6 also improves type inference involving contravariant positions:
tsfunction combine<T>(...funcs: ((x: T) => void)[]): (x: T) => void { return x => { for (const f of funcs) f(x); } } function animalFunc(x: Animal) {} function dogFunc(x: Dog) {} let combined = combine(animalFunc, dogFunc); // (x: Dog) => void
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.
Support for JSX Fragment Syntax
TypeScript 2.6.2 adds support for the new <>...</>
syntax for fragments in JSX.
It is frequently desirable to return multiple children from a component.
However, this is invalid, so the usual approach has been to wrap the text in an extra element, such as a <div>
or <span>
as shown below.
tsxrender() { 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:
tsxrender() { return ( <> Some text. <h2>A heading</h2> More text. </> ); }
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.
Cache tagged template objects in modules
TypeScript 2.6 fixes the tagged string template emit to align better with the ECMAScript spec.
As per the ECMAScript spec, every time a template tag is evaluated, the same template strings object (the same TemplateStringsArray
) should be passed as the first argument.
Before TypeScript 2.6, the generated output was a completely new template object each time.
Though the string contents are the same, this emit affects libraries that use the identity of the string for cache invalidation purposes, e.g. lit-html.
Example
tsexport function id(x: TemplateStringsArray) { return x; } export function templateObjectFactory() { return id`hello world`; } let result = templateObjectFactory() === templateObjectFactory(); // true in TS 2.6
Results in the following generated code:
js"use strict"; var __makeTemplateObject = (this && this.__makeTemplateObject) || function (cooked, raw) { if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } return cooked; }; function id(x) { return x; } var _a; function templateObjectFactory() { return id(_a || (_a = __makeTemplateObject(["hello world"], ["hello world"]))); } var result = templateObjectFactory() === templateObjectFactory();
Note: This change brings a new emit helper,
__makeTemplateObject
; if you are using--importHelpers
withtslib
, an updated to version 1.8 or later.
Localized diagnostics on the command line
TypeScript 2.6 npm package ships with localized versions of diagnostic messages for 13 languages.
The localized messages are available when using --locale
flag on the command line.
Example
Error messages in Russian:
shc:\ts>tsc --v Version 2.6.0-dev.20171003 c:\ts>tsc --locale ru --pretty c:\test\a.ts ../test/a.ts(1,5): error TS2322: Тип ""string"" не может быть назначен для типа "number". 1 var x: number = "string"; ~
And help in Japanese:
shPS C:\ts> tsc --v Version 2.6.0-dev.20171003 PS C:\ts> tsc --locale ja-jp バージョン 2.6.0-dev.20171003 構文: tsc [オプション] [ファイル ...] 例: tsc hello.ts tsc --outFile file.js file.ts tsc @args.txt オプション: -h, --help このメッセージを表示します。 --all コンパイラ オプションをすべて表示します。 -v, --version コンパイラのバージョンを表示します。 --init TypeScript プロジェクトを初期化して、tsconfig.json ファイルを作成します。 -p ファイルまたはディレクトリ, --project ファイルまたはディレクトリ 構成ファイルか、'tsconfig.json' を含むフォルダーにパスが指定されたプロジェクトをコ ンパイルします。 --pretty 色とコンテキストを使用してエラーとメッセージにスタイルを適用します (試験的)。 -w, --watch 入力ファイルを監視します。 -t バージョン, --target バージョン ECMAScript のターゲット バージョンを指定します: 'ES3' (既定)、'ES5'、'ES2015'、'ES2016'、'ES2017'、'ES NEXT'。 -m 種類, --module 種類 モジュール コード生成を指定します: 'none'、'commonjs'、'amd'、'system'、'umd'、'es2015'、'ESNext'。 --lib コンパイルに含めるライブラリ ファイルを指定します: 'es5' 'es6' 'es2015' 'es7' 'es2016' 'es2017' 'esnext' 'dom' 'dom.iterable' 'webworker' 'scripthost' 'es201 5.core' 'es2015.collection' 'es2015.generator' 'es2015.iterable' 'es2015.promise' 'es2015.proxy' 'es2015.reflect' 'es2015.symbol' 'es2015.symbol.wellkno wn' 'es2016.array.include' 'es2017.object' 'es2017.sharedmemory' 'es2017.string' 'es2017.intl' 'esnext.asynciterable' --allowJs javascript ファイルのコンパイルを許可します。 --jsx 種類 JSX コード生成を指定します: 'preserve'、'react-native'、'react'。 -d, --declaration 対応する '.d.ts' ファイルを生成します。 --sourceMap 対応する '.map' ファイルを生成します。 --outFile ファイル 出力を連結して 1 つのファイルを生成します。 --outDir ディレクトリ ディレクトリへ出力構造をリダイレクトします。 --removeComments コメントを出力しないでください。 --noEmit 出力しないでください。 --strict strict 型チェックのオプションをすべて有効にします。 --noImplicitAny 暗黙的な 'any' 型を含む式と宣言に関するエラーを発生させます。 --strictNullChecks 厳格な null チェックを有効にします。 --noImplicitThis 暗黙的な 'any' 型を持つ 'this' 式でエラーが発生します。 --alwaysStrict 厳格モードで解析してソース ファイルごとに "use strict" を生成します。 --noUnusedLocals 使用されていないローカルに関するエラーを報告します。 --noUnusedParameters 使用されていないパラメーターに関するエラーを報告します。 --noImplicitReturns 関数の一部のコード パスが値を返さない場合にエラーを報告します。 --noFallthroughCasesInSwitch switch ステートメントに case のフォールスルーがある場合にエラーを報告します。 --types コンパイルに含む型宣言ファイル。 @<ファイル>
Suppress errors in .ts files using ’// @ts-ignore’ comments
TypeScript 2.6 support suppressing errors in .js files using // @ts-ignore
comments placed above the offending lines.
Example
tsif (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.
Faster tsc --watch
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.
Write-only references now flagged as unused
TypeScript 2.6 adds revised implementation the --noUnusedLocals
and --noUnusedParameters
compiler options.
Declarations are only written to but never read from are now flagged as unused.
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.
tsfunction f(n: number) { n = 0; } class C { private m: number; constructor() { this.m = 0; } }
Also functions that are only called within their own bodies are considered unused.
Example
tsfunction f() { f(); // Error: 'f' is declared but its value is never read }
TypeScript 2.5
Optional catch
clause variables
Thanks to work done by @tinganho, TypeScript 2.5 implements a new ECMAScript feature that allows users to omit the variable in catch
clauses.
For example, when using JSON.parse
you may need to wrap calls to the function with a try
/catch
, but you may not end up using the SyntaxError
that gets thrown when input is erroneous.
tslet input = "..."; try { JSON.parse(input); } catch { // ^ Notice that our `catch` clause doesn't declare a variable. console.log("Invalid JSON given\n\n" + input) }
Type assertion/cast syntax in checkJs
/@ts-check
mode
TypeScript 2.5 introduces the ability to assert the type of expressions when using plain JavaScript in your projects.
The syntax is an /** @type {...} */
annotation comment followed by a parenthesized expression whose type needs to be re-evaluated.
For example:
tsvar x = /** @type {SomeType} */ (AnyParenthesizedExpression);
Deduplicated and redirected packages
When importing using the Node
module resolution strategy in TypeScript 2.5, the compiler will now check whether files originate from “identical” packages.
If a file originates from a package with a package.json
containing the same name
and version
fields as a previously encountered package, then TypeScript will redirect itself to the top-most package.
This helps resolve problems where two packages might contain identical declarations of classes, but which contain private
members that cause them to be structurally incompatible.
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.
The --preserveSymlinks
compiler flag
TypeScript 2.5 brings the preserveSymlinks
flag, which parallels the behavior of the --preserve-symlinks
flag in Node.js.
This flag also exhibits the opposite behavior to Webpack’s resolve.symlinks
option (i.e. setting TypeScript’s preserveSymlinks
to true
parallels setting Webpack’s resolve.symlinks
to false
, and vice-versa).
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
Dynamic Import Expressions
Dynamic import
expressions are a new feature and part of ECMAScript that allows users to asynchronously request a module at any arbitrary point in your program.
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:
tsasync function getZipFile(name: string, files: File[]): Promise<File> { const zipUtil = await import('./utils/create-zip-file'); const zipContents = await zipUtil.getContentAsBlob(files); return new File(zipContents, name); }
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.
tsenum 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"
.
Improved inference for generics
TypeScript 2.4 introduces a few wonderful changes around the way generics are inferred.
Return types as inference targets
For one, TypeScript can now make inferences for the return type of a call. This can improve your experience and catch errors. Something that now works:
tsfunction arrayMap<T, U>(f: (x: T) => U): (a: T[]) => U[] { return a => a.map(f); } const lengths: (a: string[]) => number[] = arrayMap(s => s.length);
As an example of new errors you might spot as a result:
tslet x: Promise<string> = new Promise(resolve => { resolve(10); // ~~ Error! });
Type parameter inference from contextual types
Prior to TypeScript 2.4, in the following example
tslet f: <T>(x: T) => T = y => y;
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:
tslet f: <T>(x: T) => T = y => y() + y.foo.bar;
That last example isn’t actually type-safe.
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.
Stricter checking for generic functions
TypeScript now tries to unify type parameters when comparing two single-signature types. As a result, you’ll get stricter checks when relating two generic signatures, and may catch some bugs.
tstype A = <T, U>(x: T, y: U) => [T, U]; type B = <S>(x: S, y: S) => [S, S]; function f(a: A, b: B) { a = b; // Error b = a; // Ok }
Strict contravariance for callback parameters
TypeScript has always compared parameters in a bivariant way.
There are a number of reasons for this, but by-and-large this was not been a huge issue for our users until we saw some of the adverse effects it had with Promise
s and Observable
s.
TypeScript 2.4 introduces tightens this up when relating two callback types. For example:
tsinterface Mappable<T> { map<U>(f: (x: T) => U): Mappable<U>; } declare let a: Mappable<number>; declare let b: Mappable<string | number>; 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.
Weak Type Detection
TypeScript 2.4 introduces the concept of “weak types”.
Any type that contains nothing but a set of all-optional properties is considered to be weak.
For example, this Options
type is a weak type:
tsinterface 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:
tsfunction sendMessage(options: Options) { // ... } 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:
- Declare the properties if they really do exist.
- Add an index signature to the weak type (i.e.
[propName: string]: {}
). - Use a type assertion (i.e.
opts as Options
).
TypeScript 2.3
Generators and Iteration for ES5/ES3
First some ES2016 terminology:
Iterators
ES2015 introduced Iterator
, which is an object that exposes three methods, next
, return
, and throw
, as per the following interface:
tsinterface 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:
tsfunction* 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
orSymbol.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:
tsinterface 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:
tsasync function* g() { yield 1; await sleep(100); yield* [2, 3]; yield* (async function *() { await sleep(100); yield 4; })(); }
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
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:
tsasync function f() { for await (const x of g()) { console.log(x); } }
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 polyfillSymbol.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 theAsyncIterator
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.
Generic parameter defaults
TypeScript 2.3 adds support for declaring defaults for generic type parameters.
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:
tsdeclare function create(): Container<HTMLDivElement, HTMLDivElement[]>; declare function create<T extends HTMLElement>(element: T): Container<T, T[]>; declare function create<T extends HTMLElement, U extends HTMLElement>(element: T, children: U[]): Container<T, U[]>;
With generic parameter defaults we can reduce it to:
tsdeclare function create<T extends HTMLElement = HTMLDivElement, U = T[]>(element?: T, children?: U): Container<T, U>;
A generic parameter default follows the following rules:
- A type parameter is deemed optional if it has a default.
- Required type parameters must not follow optional type parameters.
- Default types for a type parameter must satisfy the constraint for the type parameter, if it exists.
- When specifying type arguments, you are only required to specify type arguments for the required type parameters. Unspecified type parameters will resolve to their default types.
- If a default type is specified and inference cannot chose a candidate, the default type is inferred.
- A class or interface declaration that merges with an existing class or interface declaration may introduce a default for an existing type parameter.
- A class or interface declaration that merges with an existing class or interface declaration may introduce a new type parameter as long as it specifies a default.
New --strict
master option
New checks added to TypeScript are often off by default to avoid breaking existing projects. While avoiding breakage is a good thing, this strategy has the drawback of making it increasingly complex to choose the highest level of type safety, and doing so requires explicit opt-in action on every TypeScript release. With the --strict
option it becomes possible to choose maximum type safety with the understanding that additional errors might be reported by newer versions of the compiler as improved type checking features are added.
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,
--strict --noImplicitThis false
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.
Enhanced --init
output
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.
Errors in .js files with --checkJs
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.
See Type checking JavaScript Files documentation for more details.
TypeScript 2.2
Support for Mix-in classes
TypeScript 2.2 adds support for the ECMAScript 2015 mixin class pattern (see MDN Mixin description and “Real” Mixins with JavaScript Classes for more details) as well as rules for combining mixin construct signatures with regular construct signatures in intersection types.
First some terminology:
- 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 typeX
,new (...args: any[]) => X
is a mixin constructor type with an instance typeX
. - 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 asuper(...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
.
Putting all of the above rules together in an example:
tsclass Point { constructor(public x: number, public y: number) {} } class Person { constructor(public name: string) {} } type Constructor<T> = new(...args: any[]) => T; function Tagged<T extends Constructor<{}>>(Base: T) { return class extends Base { _tag: string; constructor(...args: any[]) { super(...args); this._tag = ""; } } } const TaggedPoint = Tagged(Point); let point = new TaggedPoint(10, 20); point._tag = "hello"; class Customer extends Tagged(Person) { accountBalance: number; } let customer = new Customer("Joe"); 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
).
tsinterface Point { x: number; y: number; } const WithLocation = <T extends Constructor<Point>>(Base: T) => class extends Base { getLocation(): [number, number] { return [this.x, this.y]; } }
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:
tsdeclare function create(o: object | null): void; create({ prop: 0 }); // OK create(null); // OK create(42); // Error create("string"); // Error create(false); // Error create(undefined); // Error
Support for new.target
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
tsclass CustomError extends Error { constructor(message?: string) { super(message); // 'Error' breaks prototype chain here Object.setPrototypeOf(this, new.target.prototype); // restore prototype chain } }
This results in the generated JS
jsvar CustomError = (function (_super) { __extends(CustomError, _super); function CustomError() { var _newTarget = this.constructor; var _this = _super.apply(this, arguments); // 'Error' breaks prototype chain here _this.__proto__ = _newTarget.prototype; // restore prototype chain return _this; } return CustomError; })(Error);
new.target
also comes in handy for writing constructable functions, for example:
tsfunction f() { if (new.target) { /* called via 'new' */ } }
Which translates to:
jsfunction f() { var _newTarget = this && this instanceof f ? this.constructor : void 0; if (_newTarget) { /* called via 'new' */ } }
Better checking for null
/undefined
in operands of expressions
TypeScript 2.2 improves checking of nullable operands in expressions. Specifically, these are now flagged as errors:
- If either operand of a
+
operator is nullable, and neither operand is of typeany
orstring
. - If either operand of a
-
,*
,**
,/
,%
,<<
,>>
,>>>
,&
,|
, or^
operator is nullable. - If either operand of a
<
,>
,<=
,>=
, orin
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
disappear from unions in classic type checking mode.
Dotted property for types with string index signatures
Types with a string index signature can be indexed using the []
notation, but were not allowed to use the .
. Starting with TypeScript 2.2 using either should be allowed.
tsinterface StringMap<T> { [x: string]: T; } const map: StringMap<number>; 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.
Support for spread operator on JSX element children
TypeScript 2.2 adds support for using spread on a JSX element children. Please see facebook/jsx#57 for more details.
Example
tsfunction Todo(prop: { key: number, todo: string }) { return <div>{prop.key.toString() + prop.todo}</div>; } function TodoList({ todos }: TodoListProps) { return <div> {...todos.map(todo => <Todo key={todo.id} todo={todo.todo} />)} </div>; } let x: TodoListProps; <TodoList {...x} />
New jsx: react-native
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
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 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
tsinterface Person { name: string; age: number; location: string; } type K1 = keyof Person; // "name" | "age" | "location" type K2 = keyof Person[]; // "length" | "push" | "pop" | "concat" | ... type K3 = keyof { [x: string]: Person }; // 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
tstype P1 = Person["name"]; // string type P2 = Person["name" | "age"]; // string | number type P3 = string["charAt"]; // (pos: number) => string type P4 = string[]["push"]; // (...items: string[]) => number type P5 = string[][0]; // string
You can use this pattern with other parts of the type system to get type-safe lookups.
tsfunction getProperty<T, K extends keyof T>(obj: T, key: K) { return obj[key]; // Inferred type is T[K] } function setProperty<T, K extends keyof T>(obj: T, key: K, value: T[K]) { obj[key] = value; } let x = { foo: 10, bar: "hello!" }; let foo = getProperty(x, "foo"); // number let bar = getProperty(x, "bar"); // string let oops = 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 a `Person:
tsinterface Person { name: string; age: number; location: string; }
A partial version of it would be:
tsinterface PartialPerson { name?: string; age?: number; location?: string; }
with Mapped types, PartialPerson
can be written as a generalized transformation on the type Person
as:
tstype Partial<T> = { [P in keyof T]?: T[P]; }; type PartialPerson = 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 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:
ts// Keep types the same, but make each property to be read-only. type Readonly<T> = { readonly [P in keyof T]: T[P]; }; // Same property names, but make the value a promise instead of a concrete one type Deferred<T> = { [P in keyof T]: Promise<T[P]>; }; // Wrap proxies around properties of T type Proxify<T> = { [P in keyof T]: { get(): T[P]; set(v: T[P]): void } };
Partial
, Readonly
, Record
, and Pick
Partial
and Readonly
, as described earlier, are very useful constructs.
You can use them to describe some common JS routines like:
tsfunction assign<T>(obj: T, props: Partial<T>): void; function freeze<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
and Pick
.
ts// From T pick a set of properties K declare function pick<T, K extends keyof T>(obj: T, ...keys: K[]): Pick<T, K>; const nameAndAgeOnly = pick(person, "name", "age"); // { name: string, age: number }
ts// For every properties K of type T, transform it to U function mapObject<K extends string, T, U>(obj: Record<K, T>, f: (x: T) => U): Record<K, U> const names = { foo: "hello", bar: "world", baz: "bye" }; const lengths = mapObject(names, s => s.length); // { foo: number, bar: number, baz: number }
Object Spread and Rest
TypeScript 2.1 brings support for ES2017 Spread and Rest.
Similar to array spread, spreading an object can be handy to get a shallow copy:
tslet copy = { ...original };
Similarly, you can merge several different objects.
In the following example, merged
will have properties from foo
, bar
, and baz
.
tslet merged = { ...foo, ...bar, ...baz };
You can also override existing properties and add new ones:
tslet obj = { x: 1, y: "string" }; var newObj = {...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:
tslet obj = { 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 grabbing a 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
flag to something like"dom", "es2015"
or"dom", "es2015.promise", "es5"
Example
tsconfig.json
json{ "compilerOptions": { "lib": ["dom", "es2015.promise", "es5"] } }
dramaticWelcome.ts
tsfunction delay(milliseconds: number) { return new Promise<void>(resolve => { setTimeout(resolve, milliseconds); }); } async function dramaticWelcome() { console.log("Hello"); for (let i = 0; i < 3; i++) { await delay(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 in every 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 the tslib
utility library:
shnpm install tslib
Second, compile your files using --importHelpers
:
shtsc --module commonjs --importHelpers a.ts
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.
tsexport const o = { a: 1, name: "o" }; export const copy = { ...o };
js"use strict"; var tslib_1 = require("tslib"); exports.o = { a: 1, name: "o" }; exports.copy = tslib_1.__assign({}, exports.o);
Untyped imports
TypeScript has traditionally been overly strict about how you can import modules. This was to avoid typos and prevent users from using modules incorrectly.
However, a lot of the time, you might just want to import an existing module that may not have its own .d.ts
file.
Previously this was an error.
Starting with TypeScript 2.1 this is now much easier.
With TypeScript 2.1, you can import a JavaScript module without needing a type declaration.
A type 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
ts// Succeeds if `node_modules/asdf/index.js` exists, or if `node_modules/asdf/package.json` defines a valid "main" entry point import { 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 like async
/await
.
--target ESNext
targets latest supported ES proposed features.
Improved any
Inference
Previously, if TypeScript couldn’t figure out the type of a variable, it would choose the any
type.
tslet x; // implicitly 'any' let y = []; // implicitly 'any[]' let z: any; // explicitly 'any'.
With TypeScript 2.1, instead of just choosing any
, TypeScript will infer types based on what you end up assigning later on.
This is only enabled if --noImplicitAny
is set.
Example
tslet x; // You can still assign anything you want to 'x'. x = () => 42; // 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'. // 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 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.
tsfunction 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 }
Implicit any errors
One great benefit of this is that you’ll see way fewer implicit any
errors when running with --noImplicitAny
.
Implicit any
errors are only reported when the compiler is unable to know the type of a variable without a type annotation.
Example
tsfunction 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. } }
Better inference for literal types
String, numeric and boolean literal types (e.g. "abc"
, 1
, and true
) were previously inferred only in the presence of an explicit type annotation.
Starting with TypeScript 2.1, literal types are always inferred for const
variables and readonly
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
tsconst 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" let v1 = 1; // Type number let v2 = c2; // Type number let v3 = c3; // Type string let v4 = c4; // Type boolean let v5 = 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, 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
tsconst c1 = "hello"; // Widening type "hello" let v1 = c1; // Type string const c2: "hello" = "hello"; // Type "hello" let v2 = c2; // Type "hello"
Use returned values from super calls as ‘this’
In ES2015, constructors which return an object implicitly substitute the value of this
for any callers of super()
.
As a result, it is necessary to capture any potential return value of super()
and replace it with this
.
This change enables working with Custom Elements, which takes advantage of this to initialize browser-allocated elements with user-written constructors.
Example
tsclass Base { x: number; constructor() { // return a new object other than `this` return { x: 1, }; } } class Derived extends Base { constructor() { super(); this.x = 2; } }
Generates:
jsvar Derived = (function (_super) { __extends(Derived, _super); function Derived() { var _this = _super.call(this) || this; _this.x = 2; return _this; } 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.
TypeScript 2.1 supports inheriting configuration using extends
, 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 are 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 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
:
json{ "compilerOptions": { "noImplicitAny": true, "strictNullChecks": true } }
tsconfig.json
:
json{ "extends": "./configs/base", "files": [ "main.ts", "supplemental.ts" ] }
tsconfig.nostrictnull.json
:
json{ "extends": "./tsconfig", "compilerOptions": { "strictNullChecks": false } }
New --alwaysStrict
Invoking the compiler with --alwaysStrict
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.
TypeScript 2.0
Null- and undefined-aware types
TypeScript has two special types, Null and Undefined, that have the values null
and undefined
respectively.
Previously it was not possible to explicitly name these types, but null
and undefined
may now be used as type names regardless of type checking mode.
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
ts// Compiled with --strictNullChecks let x: number; let y: number | undefined; let z: number | null | undefined; x = 1; // Ok y = 1; // Ok z = 1; // Ok x = undefined; // Error y = undefined; // Ok z = undefined; // Ok x = null; // Error y = null; // Error z = null; // Ok x = y; // Error x = z; // Error y = x; // Ok y = z; // Error z = x; // Ok z = y; // Ok
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
ts// Compiled with --strictNullChecks let x: number; let y: number | null; let z: number | undefined; x; // Error, reference not preceded by assignment y; // Error, reference not preceded by assignment z; // Ok 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.
Optional parameters and properties
Optional parameters and properties automatically have undefined
added to their types, even when their type annotations don’t specifically include undefined
.
For example, the following two types are identical:
ts// Compiled with --strictNullChecks type T1 = (x?: number) => string; // x has type number | undefined type T2 = (x?: number | undefined) => string; // x has type number | undefined
Non-null and non-undefined type guards
A property access or a function call produces a compile-time error if the object or function is of a type that includes null
or undefined
.
However, type guards are extended to support non-null and non-undefined checks.
Example
ts// Compiled with --strictNullChecks declare function f(x: number): string; let x: number | null | undefined; if (x) { f(x); // Ok, type of x is number here } else { f(x); // Error, type of x is number? here } let a = x != null ? f(x) : ""; // Type of a is string let b = x && f(x); // Type of b is string | 0 | null | undefined
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).
Dotted names in type guards
Type guards previously only supported checking local variables and parameters. Type guards now support checking “dotted names” consisting of a variable or parameter name followed one or more property accesses.
Example
tsinterface Options { location?: { x?: number; y?: number; }; } function foo(options?: Options) { if (options && options.location && options.location.x) { const x = options.location.x; // Type of x is 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.
ts// Compiled with --strictNullChecks function sum(a: number | null, b: number | null) { return a + b; // Produces value of type number }
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.
ts// Compiled with --strictNullChecks interface Entity { name: string; } let x: Entity | null; let s = x && x.name; // s is of type string | null let y = x || { name: "test" }; // y is of type Entity
Type widening
The null
and undefined
types are not widened to any
in strict null checking mode.
tslet z = null; // Type of z is null
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
).
Non-null assertion operator
A new !
post-fix expression operator may be used to assert that its operand is non-null and non-undefined in contexts where the type checker is unable to conclude that fact.
Specifically, the operation x!
produces a value of the type of x
with null
and undefined
excluded.
Similar to type assertions of the forms <T>x
and x as T
, the !
non-null assertion operator is simply removed in the emitted JavaScript code.
ts// Compiled with --strictNullChecks function validateEntity(e?: Entity) { // Throw exception if e is null or invalid entity } function processEntity(e?: Entity) { validateEntity(e); let s = e!.name; // Assert that e is non-null and access name }
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.
Control flow based type analysis
TypeScript 2.0 implements a control flow-based type analysis for local variables and parameters.
Previously, the type analysis performed for type guards was limited to if
statements and ?:
conditional expressions and didn’t include effects of assignments and control flow constructs such as return
and break
statements.
With TypeScript 2.0, the type checker analyses all possible flows of control in statements and expressions to produce the most specific type possible (the narrowed type) at any given location for a local variable or parameter that is declared to have a union type.
Example
tsfunction foo(x: string | number | boolean) { if (typeof x === "string") { x; // type of x is string here x = 1; x; // type of x is number here } x; // type of x is number | boolean here } function bar(x: string | number) { if (typeof x === "number") { return; } x; // type of x is string here }
Control flow based type analysis is particuarly relevant in --strictNullChecks
mode because nullable types are represented using union types:
tsfunction test(x: string | null) { if (x === null) { return; } x; // type of x is string in remainder of function }
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
.
tsfunction mumble(check: boolean) { let x: number; // Type doesn't permit undefined x; // Error, x is undefined if (check) { x = 1; x; // Ok } x; // Error, x is possibly undefined x = 2; x; // Ok }
Tagged union types
TypeScript 2.0 implements support for tagged (or discriminated) union types.
Specifically, the TS compiler now support type guards that narrow union types based on tests of a discriminant property and furthermore extend that capability to switch
statements.
Example
tsinterface Square { kind: "square"; size: number; } interface Rectangle { kind: "rectangle"; width: number; height: number; } interface Circle { kind: "circle"; radius: number; } type Shape = Square | Rectangle | Circle; function area(s: Shape) { // In the following switch statement, the type of s is narrowed in each case clause // according to the value of the discriminant property, thus allowing the other properties // of that variant to be accessed without a type assertion. switch (s.kind) { case "square": return s.size * s.size; case "rectangle": return s.width * s.height; case "circle": return Math.PI * s.radius * s.radius; } } function test1(s: Shape) { if (s.kind === "square") { s; // Square } else { s; // Rectangle | Circle } } function test2(s: Shape) { if (s.kind === "square" || s.kind === "rectangle") { return; } s; // Circle }
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.
The never
type
TypeScript 2.0 introduces a new primitive type never
.
The never
type represents the type of values that never occur.
Specifically, never
is the return type for functions that never return and never
is the type of variables under type guards that are never true.
The never
type has the following characteristics:
never
is a subtype of and assignable to every type.- No type is a subtype of or assignable to
never
(exceptnever
itself). - In a function expression or arrow function with no return type annotation, if the function has no
return
statements, or onlyreturn
statements with expressions of typenever
, and if the end point of the function is not reachable (as determined by control flow analysis), the inferred return type for the function isnever
. - In a function with an explicit
never
return type annotation, allreturn
statements (if any) must have expressions of typenever
and the end point of the function must not be reachable.
Because never
is a subtype of every type, it is always omitted from union types and it is ignored in function return type inference as long as there are other types being returned.
Some examples of functions returning never
:
ts// Function returning never must have unreachable end point function error(message: string): never { throw new Error(message); } // Inferred return type is never function fail() { return error("Something failed"); } // Function returning never must have unreachable end point function infiniteLoop(): never { while (true) { } }
Some examples of use of functions returning never
:
ts// Inferred return type is number function move1(direction: "up" | "down") { switch (direction) { case "up": return 1; case "down": return -1; } return error("Should never get here"); } // Inferred return type is number function move2(direction: "up" | "down") { return direction === "up" ? 1 : direction === "down" ? -1 : error("Should never get here"); } // Inferred return type is T function check<T>(x: T | undefined) { return x || error("Undefined value"); }
Because never
is assignable to every type, a function returning never
can be used when a callback returning a more specific type is required:
tsfunction test(cb: () => string) { let s = cb(); return s; } test(() => "hello"); test(() => fail()); test(() => { throw new Error(); })
Read-only properties and index signatures
A property or index signature can now be declared with the readonly
modifier is considered read-only.
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.
In addition, entities are implicitly read-only in several situations:
- A property declared with a
get
accessor and noset
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 whenfoo
is declared asimport * as foo from "foo"
).
Example
tsinterface Point { readonly x: number; readonly y: number; } var p1: Point = { x: 10, y: 20 }; p1.x = 5; // Error, p1.x is read-only 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
tsclass Foo { readonly a = 1; readonly b: string; constructor() { this.b = "hello"; // Assignment permitted in constructor } }
tslet a: Array<number> = [0, 1, 2, 3, 4]; let b: ReadonlyArray<number> = a; b[5] = 5; // Error, elements are read-only b.push(5); // Error, no push method (because it mutates array) b.length = 3; // Error, length is read-only a = b; // Error, mutating methods are missing
Specifying the type of this
for functions
Following up on specifying the type of this
in a class or an interface, functions and methods can now declare the type of this
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:
tsfunction f(this: void) { // make sure `this` is unusable in this standalone function }
this
parameters in callbacks
Libraries can also use this
parameters to declare how callbacks will be invoked.
Example
tsinterface 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.
Now if you annotate calling code with this
:
tsclass 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 in tsconfig.json
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
json{ "compilerOptions": { "module": "commonjs", "noImplicitAny": true, "removeComments": true, "preserveConstEnums