TypeScript 2.7
Constant-named properties
TypeScript 2.7 adds support for declaring const-named properties on types including ECMAScript symbols.
Example
ts// LibexportconstSERIALIZE =Symbol("serialize-method-key");exportinterfaceSerializable {[SERIALIZE](obj: {}):string;}
ts// consumerimport {SERIALIZE,Serializable }from"lib";classJSONSerializableItemimplementsSerializable {[SERIALIZE](obj: {}) {returnJSON.stringify(obj);}}
This also applies to numeric and string literals.
Example
tsconstFoo ="Foo";constBar ="Bar";letx = {[Foo]:100,[Bar]:"hello"};leta =x[Foo];// has type 'number'letb =x[Bar];// has type 'string'
unique symbol
To enable treating symbols as unique literals a new typeunique symbol is available.unique symbol is a subtype ofsymbol, and are produced only from callingSymbol() orSymbol.for(), or from explicit type annotations.The new type is only allowed onconst declarations andreadonly static properties, and in order to reference a specific unique symbol, you’ll have to use thetypeof operator.Each reference to aunique symbol implies a completely unique identity that’s tied to a given declaration.
Example
ts// WorksdeclareconstFoo:uniquesymbol;// Error! 'Bar' isn't a constant.letBar:uniquesymbol =Symbol();// Works - refers to a unique symbol, but its identity is tied to 'Foo'.letBaz:typeofFoo =Foo;// Also works.classC {staticreadonlyStaticSymbol:uniquesymbol =Symbol();}
Because eachunique symbol has a completely separate identity, no twounique symbol types are assignable or comparable to each other.
Example
tsconstFoo =Symbol();constBar =Symbol();// Error: can't compare two unique symbols.if (Foo ===Bar) {// ...}
Strict Class Initialization
TypeScript 2.7 introduces a new flag calledstrictPropertyInitialization.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
tsclassC {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 forbaz to potentially beundefined, we should have declared it with the typeboolean | 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 newdefinite assignment assertion modifiers for your properties (discussed below).
tsclassC {foo!:number;// ^// Notice this '!' modifier.// This is the "definite assignment assertion"constructor() {this.initialize();}initialize() {this.foo =0;}}
Keep in mind thatstrictPropertyInitialization will be turned on along with otherstrict mode flags, which can impact your project.You can set thestrictPropertyInitialization setting tofalse in yourtsconfig.json’scompilerOptions, 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.
Example
tsletx:number;initialize();console.log(x +x);// ~ ~// Error! Variable 'x' is used before being assigned.functioninitialize() {x =10;}
With definite assignment assertions, we can assert thatx is really assigned by appending an! to its declaration:
ts// Notice the '!'letx!:number;initialize();// No error!console.log(x +x);functioninitialize() {x =10;}
In a sense, the definite assignment assertion operator is the dual of the non-null assertion operator (in whichexpressions are post-fixed with a!), which we could also have used in the example.
tsletx:number;initialize();// No error!console.log(x! +x!);functioninitialize() {x =10;}
In our example, we knew that all uses ofx 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 fromKiara Grouwstra, tuple types now encode their arity into the type of their respectivelength 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 ofNumStrTuple:
tsinterfaceNumStrTupleextendsArray<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 alength property, falling back tonumber.
tsinterfaceMinimumNumStrTupleextendsArray<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 nownormalize the object literal types such that all properties are present in each constituent of the union type.
Consider:
tsconstobj =test ? {text:"hello" } : {};// { text: string } | { text?: undefined }consts =obj.text;// string | undefined
Previously type{} was inferred forobj and the second line subsequently caused an error becauseobj 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 }letobj = [{a:1,b:2 }, {a:"abc" }, {}][0];obj.a;// string | number | undefinedobj.b;// number | undefined
Multiple object literal type inferences for the same type parameter are similarly collapsed into a single normalized union type:
tsdeclarefunctionf<T>(...items:T[]):T;// let obj: { a: number, b: number } |// { a: string, b?: undefined } |// { a?: undefined, b?: undefined }letobj =f({a:1,b:2 }, {a:"abc" }, {});obj.a;// string | number | undefinedobj.b;// number | undefined
Improved handling of structurally identical classes andinstanceof expressions
TypeScript 2.7 improves the handling of structurally identical classes in union types andinstanceof 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 ofand derives from another class type in the union.
- Type checking of the
instanceofoperator is now based on whether the type of the left operandderives from the type indicated by the right operand (as opposed to a structural subtype check).
This means that union types andinstanceof properly distinguish between structurally identical classes.
Example
tsclassA {}classBextendsA {}classCextendsA {}classDextendsA {c:string;}classEextendsD {}letx1 = !true ?newA() :newB();// Aletx2 = !true ?newB() :newC();// B | C (previously B)letx3 = !true ?newC() :newD();// C | D (previously C)leta1 = [newA(),newB(),newC(),newD(),newE()];// A[]leta2 = [newB(),newC(),newD(),newE()];// (B | C | D)[] (previously B[])functionf1(x:B |C |D) {if (xinstanceofB) {x;// B (previously B | D)}elseif (xinstanceofC) {x;// C}else {x;// D (previously never)}}
Type guards inferred fromin operator
Thein operator now acts as a narrowing expression for types.
For an in x expression, wheren is a string literal or string literal type andx is a union type, the “true” branch narrows to types which have an optional or required propertyn, and the “false” branch narrows to types which have an optional or missing propertyn.
Example
tsinterfaceA {a:number;}interfaceB {b:string;}functionfoo(x:A |B) {if ("a"inx) {returnx.a;}returnx.b;}
Support forimport d from "cjs" from 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 underesModuleInterop.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 (fooin 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 adefaultexport, 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 newesModuleInterop these two issues should be addressed:
- A namespace import (i.e.
import * as foo from "foo") is now correctly flagged as uncallable. Calling it will result in an error. - Default imports to CommonJS/AMD/UMD are now allowed (e.g.
import fs from "fs"), and should work as expected.
Note: The new behavior is added under a flag to avoid unwarranted breaks to existing code bases.We highly recommend applying it both to new and existing projects.For existing projects, namespace imports (
import * as express from "express"; express();) will need to be converted to default imports (import express from "express"; express();).
Example
WithesModuleInterop two new helpers are generated__importStar and__importDefault for import* and importdefault respectively.For instance input like:
tsimport*asfoofrom"foo";importbfrom"bar";
Will generate:
js"use strict";var__importStar =(this &&this.__importStar) ||function(mod) {if (mod &&mod.__esModule)returnmod;varresult = {};if (mod !=null)for (varkinmod)if (Object.hasOwnProperty.call(mod,k))result[k] =mod[k];result["default"] =mod;returnresult;};var__importDefault =(this &&this.__importDefault) ||function(mod) {returnmod &&mod.__esModule ?mod : {default:mod };};exports.__esModule =true;varfoo =__importStar(require("foo"));varbar_1 =__importDefault(require("bar"));
Numeric separators
TypeScript 2.7 brings support forES Numeric Separators.Numeric literals can now be separated into segments using_.
Example
tsconstmillion =1_000_000;constphone =555_734_2231;constbytes =0xff_0c_00_ff;constword =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’spretty 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).
The TypeScript docs are an open source project. Help us improve these pagesby sending a Pull Request ❤
Last updated: Dec 16, 2025