We’re thrilled to announce the release of TypeScript 3.7, a release packed with awesome new language, compiler, and tooling features.
If you haven’t yet heard of TypeScript, it’s a language based on JavaScript that adds static type-checking along with type syntax. Static type-checking lets us know about problems with our code before we try to run it by reporting errors if we do something questionable. This ranges from type coercions that can happen in code like 42 / "hello", or even basic typos on property names. But beyond this, TypeScript powers things like completions, quick fixes, and refactorings for both TypeScript and JavaScript in some of your favorite editors. In fact, if you already use Visual Studio or Visual Studio Code, you might already be using TypeScript when you write JavaScript code! So if you’re interested in learning more, , or use npm with the following command:
npm install typescriptYou can also get editor support by
- where we’ve added an entire menu for learning what’s new.

Without further ado, let’s dive in and look at what’s new!
Optional Chaining
TypeScript 3.7 implements one of the most highly-demanded ECMAScript features yet: optional chaining!
Optional chaining is and (if it ever was); however, it has a bug because it uses
||.
CODEfunction initializeAudio() {
let volume = localStorage.volume || 0.5
// ...
}
When
localStorage.volumeis set to0, the page will set the volume to0.5which is unintended.??avoids some unintended behavior from0,NaNand""being treated as falsy values.
We owe a large thanks to community members for implementing this feature! For more details, .
Assertion Functions
There’s a specific set of functions that
throwan error if something unexpected happened. They’re called “assertion” functions. As an example, Node.js has a dedicated function for this calledassert.
CODEassert(someValue === 42);
In this example if
someValueisn’t equal to42, thenassertwill throw anAssertionError.
Assertions in JavaScript are often used to guard against improper types being passed in. For example,
CODEfunction multiply(x, y) {
assert(typeof x === "number");
assert(typeof y === "number");
return x * y;
}
Unfortunately in TypeScript these checks could never be properly encoded. For loosely-typed code this meant TypeScript was checking less, and for slightly conservative code it often forced users to use type assertions..
CODEfunction yell(str) {
assert(typeof str === "string");
return str.toUppercase();
// Oops! We misspelled 'toUpperCase'.
// Would be great if TypeScript still caught this!
}
The alternative was to instead rewrite the code so that the language could analyze it, but this isn’t convenient.
CODEfunction yell(str) {
if (typeof str !== "string") {
throw new TypeError("str should have been a string.")
}
// Error caught!
return str.toUppercase();
}
Ultimately the goal of TypeScript is to type existing JavaScript constructs in the least disruptive way. For that reason, TypeScript 3.7 introduces a new concept called “assertion signatures” which model these assertion functions.
The first type of assertion signature models the way that Node’s
assertfunction works. It ensures that whatever condition is being checked must be true for the remainder of the containing scope.
CODEfunction assert(condition: any, msg?: string): asserts condition {
if (!condition) {
throw new AssertionError(msg)
}
}
asserts conditionsays that whatever gets passed into theconditionparameter must be true if theassertreturns (because otherwise it would throw an error). That means that for the rest of the scope, that condition must be truthy. As an example, using this assertion function means we do catch our originalyellexample.
CODEfunction yell(str) {
assert(typeof str === "string");
return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}
function assert(condition: any, msg?: string): asserts condition {
if (!condition) {
throw new AssertionError(msg)
}
}
The other type of assertion signature doesn’t check for a condition, but instead tells TypeScript that a specific variable or property has a different type.
CODEfunction assertIsString(val: any): asserts val is string {
if (typeof val !== "string") {
throw new AssertionError("Not a string!");
}
}
Here
asserts val is stringensures that after any call toassertIsString, any variable passed in will be known to be astring.
CODEfunction yell(str: any) {
assertIsString(str);
// Now TypeScript knows that 'str' is a 'string'.
return str.toUppercase();
// ~~~~~~~~~~~
// error: Property 'toUppercase' does not exist on type 'string'.
// Did you mean 'toUpperCase'?
}
These assertion signatures are very similar to writing type predicate signatures:
CODEfunction isString(val: any): val is string {
return typeof val === "string";
}
function yell(str: any) {
if (isString(str)) {
return str.toUppercase();
}
throw "Oops!";
}
And just like type predicate signatures, these assertion signatures are incredibly expressive. We can express some fairly sophisticated ideas with these.
CODEfunction assertIsDefined<T>(val: T): asserts val is NonNullable<T> {
if (val === undefined || val === null) {
throw new AssertionError(
`Expected 'val' to be defined, but received ${val}`
);
}
}
To read up more about assertion signatures, is specified to return
never.
In order to ensure that a function never potentially returned
undefinedor effectively returned from all code paths, TypeScript needed some syntactic signal – either areturnorthrowat the end of a function. So users found themselvesreturn-ing their failure functions.
CODEfunction dispatch(x: string | number): SomeType {
if (typeof x === "string") {
return doThingWithString(x);
}
else if (typeof x === "number") {
return doThingWithNumber(x);
}
return process.exit(1);
}
Now when these
never-returning functions are called, TypeScript recognizes that they affect the control flow graph and accounts for them.
CODEfunction dispatch(x: string | number): SomeType {
if (typeof x === "string") {
return doThingWithString(x);
}
else if (typeof x === "number") {
return doThingWithNumber(x);
}
process.exit(1);
}
As with assertion functions, you can .
(More) Recursive Type Aliases
Type aliases have always had a limitation in how they could be “recursively” referenced. The reason is that any use of a type alias needs to be able to substitute itself with whatever it aliases. In some cases, that’s not possible, so the compiler rejects certain recursive aliases like the following:
CODEtype Foo = Foo;
This is a reasonable restriction because any use of
Foowould need to be replaced withFoowhich would need to be replaced withFoowhich would need to be replaced withFoowhich… well, hopefully you get the idea! In the end, there isn’t a type that makes sense in place ofFoo.
This is fairly .
The
useDefineForClassFieldsFlag and ThedeclareProperty Modifier
Back when TypeScript implemented public class fields, we assumed to the best of our abilities that the following code
CODEclass C {
foo = 100;
bar: string;
}
would be equivalent to a similar assignment within a constructor body.
CODEclass C {
constructor() {
this.foo = 100;
}
}
Unfortunately, while this seemed to be the direction that the proposal moved towards in its earlier days, there is an extremely strong chance that public class fields will be standardized differently. Instead, the original code sample might need to de-sugar to something closer to the following:
CODEclass C {
constructor() {
Object.defineProperty(this, "foo", {
enumerable: true,
configurable: true,
writable: true,
value: 100
});
Object.defineProperty(this, "bar", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
}
}
While TypeScript 3.7 isn’t changing any existing emit by default, we’ve been rolling out changes incrementally to help users mitigate potential future breakage. We’ve provided a new flag called
useDefineForClassFieldsto enable this emit mode with some new checking logic.
The two biggest changes are the following:
- Declarations are initialized with
Object.defineProperty. - Declarations are always initialized to
undefined, even if they have no initializer.
This can cause quite a bit of fallout for existing code that use inheritance. First of all,
setaccessors from base classes won’t get triggered – they’ll be completely overwritten.
CODEclass Base {
set data(value: string) {
console.log("data changed to " + value);
}
}
class Derived extends Base {
// No longer triggers a 'console.log'
// when using 'useDefineForClassFields'.
data = 10;
}
Secondly, using class fields to specialize properties from base classes also won’t work.
CODEinterface Animal { animalStuff: any }
interface Dog extends Animal { dogStuff: any }
class AnimalHouse {
resident: Animal;
constructor(animal: Animal) {
this.resident = animal;
}
}
class DogHouse extends AnimalHouse {
// Initializes 'resident' to 'undefined'
// after the call to 'super()' when
// using 'useDefineForClassFields'!
resident: Dog;
constructor(dog: Dog) {
super(dog);
}
}
What these two boil down to is that mixing properties with accessors is going to cause issues, and so will re-declaring properties with no initializers.
To detect the issue around accessors, TypeScript 3.7 will now emit
get/setaccessors in.d.tsfiles so that TypeScript can check for overridden accessors.
Code that’s impacted by the class fields change can get around the issue by converting field initializers to assignments in constructor bodies.
CODEclass Base {
set data(value: string) {
console.log("data changed to " + value);
}
}
class Derived extends Base {
constructor() {
this.data = 10;
}
}
To help mitigate the second issue, you can either add an explicit initializer or add a
declaremodifier to indicate that a property should have no emit.
CODEinterface Animal { animalStuff: any }
interface Dog extends Animal { dogStuff: any }
class AnimalHouse {
resident: Animal;
constructor(animal: Animal) {
this.resident = animal;
}
}
class DogHouse extends AnimalHouse {
declare resident: Dog;
// ^^^^^^^
// 'resident' now has a 'declare' modifier,
// and won't produce any output code.
constructor(dog: Dog) {
super(dog);
}
}
Currently
useDefineForClassFieldsis only available when targeting ES5 and upwards, sinceObject.definePropertydoesn’t exist in ES3. To achieve similar checking for issues, you can create a seperate project that targets ES5 and uses--noEmitto avoid a full build.
For more information, you can .
Uncalled Function Checks
A common and dangerous error is to forget to invoke a function, especially if the function has zero arguments or is named in a way that implies it might be a property rather than a function.
CODEinterface User {
isAdministrator(): boolean;
notify(): void;
doNotDisturb?(): boolean;
}
// later...
// Broken code, do not use!
function doAdminThing(user: User) {
// oops!
if (user.isAdministrator) {
sudo();
editTheConfiguration();
}
else {
throw new AccessDeniedError("User is not an admin");
}
}
Here, we forgot to call
isAdministrator, and the code incorrectly allows non-adminstrator users to edit the configuration!
In TypeScript 3.7, this is identified as a likely error:
CODEfunction doAdminThing(user: User) {
if (user.isAdministrator) {
// ~~~~~~~~~~~~~~~~~~~~
// error! This condition will always return true since the function is always defined.
// Did you mean to call it instead?
This check is a breaking change, but for that reason the checks are very conservative. This error is only issued in
ifconditions, and it is not issued on optional properties, ifstrictNullChecksis off, or if the function is later called within the body of theif:
CODEinterface User {
isAdministrator(): boolean;
notify(): void;
doNotDisturb?(): boolean;
}
function issueNotification(user: User) {
if (user.doNotDisturb) {
// OK, property is optional
}
if (user.notify) {
// OK, called the function
user.notify();
}
}
If you intended to test the function without calling it, you can correct the definition of it to include
undefined/null, or use!!to write something likeif (!!user.isAdministrator)to indicate that the coercion is intentional.
We owe a big thanks to GitHub user and iterated to provide us with with ), so now in TypeScript 3.7, errors like this are flattened to a message like the following:
CODEType 'SomeVeryBigType' is not assignable to type 'AnotherVeryBigType'.
The types returned by 'a.b.c.d.e.f()' are incompatible between these types.
Type 'string' is not assignable to type 'number'.
For more details, , and will be available in Visual Studio 16.4 Preview 2 in the Tools Options menu.

Choosing a value of “insert” or “remove” also affects the format of auto-imports, extracted types, and other generated code provided by TypeScript services. Leaving the setting on its default value of “ignore” makes generated code match the semicolon preference detected in the current file.
Website and Playground Updates
We’ll be talking more about this in the near future, but if you haven’t seen it already, you should check out the significantly upgraded .
As a cherry on top, outside of the handbook we now have search powered by Algolia on the website, allowing you to search through the handbook, release notes, and more!

. These changes are largely correctness changes related to nullability, but impact will ultimately depend on your codebase.
Class Field Mitigations
.
Function Truthy Checks
As mentioned above, TypeScript now errors when functions appear to be uncalled within
ifstatement conditions. An error is issued when a function type is checked inifconditions unless any of the following apply:
- the checked value comes from an optional property
strictNullChecksis disabled- the function is later called within the body of the
if
Local and Imported Type Declarations Now Conflict
Due to a bug, the following construct was previously allowed in TypeScript:
CODE// ./someOtherModule.ts
interface SomeType {
y: string;
}
// ./myModule.ts
import { SomeType } from "./someOtherModule";
export interface SomeType {
x: number;
}
function fn(arg: SomeType) {
console.log(arg.x); // Error! 'x' doesn't exist on 'SomeType'
}
Here,
SomeTypeappears to originate in both theimportdeclaration and the localinterfacedeclaration. Perhaps surprisingly, inside the module,SomeTyperefers exclusively to theimported definition, and the local declarationSomeTypeis only usable when imported from another file. This is very confusing and our review of the very small number of cases of code like this in the wild showed that developers usually thought something different was happening.
In TypeScript 3.7, , and we’ll be updating our rolling feature appeared first on TypeScript.
↗ Original-Artikel auf devblogs.microsoft.com lesenVollständiger Original-BerichtAusführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf devblogs.microsoft.com. - Declarations are initialized with
SOCIAL SHARE CARD GENERATOR