So What’s New in Typescript 3.2?

A quick overview of the newest release of Microsoft's typescript version 3.2 and all the news about changes and stricter types.

Typescript

Typescript is an open-source, strongly-typed, object-oriented compiled language developed and maintained by Microsoft. It is basically JavaScript on steroids (it is a super-set of javascript) with static typing options.

It is designed for the development of large and scalable applications that compiles to JavaScript. A few days ago, the team at Microsoft released the latest version of Typescript and we are going to take a look at the new stuff 🎊

Tip: Build faster by sharing components as a team. Try Bit (open-source) to share, discover and use your components across projects and apps. It’s free.

strictBindCallApply

Earlier versions of typescript could not model the bind, call and apply methods on functions that would let binding this be possible and partially applying arguments on them and even call functions with different values for this to calling functions with an array for their arguments.

From the parameter types of version 2 the team started bringing these modeling capabilities to life, then tuple types came in version 3 opening up the ways to correctly type bind call and apply without hard-coding the whole logic. So TypeScript 3.2 ships with a new --strictBindCallApply compiler option with which the bind , call , and apply methods on function objects are strongly typed and strictly checked.

function foo(a: number, b: string): string {

return a + b;

} let a = foo.apply(undefined, [10]); // error: too few arguments

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

So here two new types are brought into the lib.d.ts file, callableFunction and NewablwFunction; they contain specialized methods declarations for the bind, call and apply for both the regular functions and constructor functions.

Generic spread expressions in object literals

If you are a javascript developer, you might have heard about spreads, a good way to copy existing properties from an object into a new object. This is done by defining an element with three periods “…” , as a super-set of javascript, typescript handles this easily anywhere it has sufficient information about the type but it would not work with generics at all. Now in TypeScript 3.2, generic spread expressions are now allowed by object literals which now produce intersection types, similar to the Object.assign function and JSX literals. For example:

function 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:

function 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 }

}

For non-generic spread expressions, Call and construct signatures have been stripped off, non-method properties are kept and properties with the same name gets the property of the rightmost property (if that makes any sense). Intersection types, however, concatenates call and construct signatures, all properties are kept and types of properties with the same name are intersected. This would clearly produce a variety of spreads for properties with the same name.

function 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

In this new version of typescript, you can de-structure a rest binding from a generic variable. Just use the predefined pick and exclude helper types in the lib.d.ts, also use the generic type as well s the names of the other bindings.

function 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 a part of an upcoming ECMAScript proposal that allow modeling theoretically arbitrarily large integers. TypeScript 3.2 ships with type-checking for BigInts along with support for emitting BigInt literals when targeting esnext. So we have a new type called bigint, just call BigInt() or write BigInt literal by adding n to the end of any integer numeric literal.

let 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.

declare 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 numbers and bigint in arithmetic operations is an error. You’ll have to explicitly convert values to BigInt

console.log(3.141592 * 10000n); // error

console.log(3145 * 10n); // error

console.log(BigInt(3145) * 10n); // okay!

Additionally, note is that bigint produces a new string when using the typeof operator: the string "bigint" . TypeScript correctly narrows using typeof as expected.

function whatKindOfNumberIsIt(x: number | bigint) {

if (typeof x === "bigint") {

console.log("'x' is a bigint!");

}

else {

console.log("'x' is a floating-point number");

}

}

Non-unit types as union discriminants

Narrowing is now easier to achieve in this new version of typescript, so the rules it considers for a discriminant property has been relaxed. Common properties of unions are now deemed discriminants as long as they contain some singleton type say a string literal, null , or undefined and they contain no generics. TypeScript 3.2 considers the error property in the following example to be a discriminant, this was not so in previous versions since Error isn’t a singleton type. With this, narrowing works correctly in the body of the unwrap function.

type 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

TypeScript has supported extending tsconfig.json files by using the extends field for a while now. This just eases ambiguity and avoids duplication of configuration, every project just has a common tsconfig.json file. Some projects, however, are published as full independent packages so ther isn’t a common tsconfig.json they can reference, developers work around it by creating separate packages and referencing the separate file.

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.

{

"extends": "@my-team/tsconfig-base",

"include": ["./**/*"]

"compilerOptions": {

// Override certain options on a project-by-project basis.

"strictBindCallApply": false,

}

}

Here, TypeScript traverses the node_modules folders looking for any @my-team/tsconfig-base package. For each of those packages found, it will first check if the package.json has a "tsconfig" field, and if it does, it will try to load a configuration file from that field. If none exists, it 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.

The new --showConfig flag

The TypeScript compiler, tsc in this new version now supports a new flag called –showConfig. If you run

tsc –-showConfig

TypeScript will calculate the effective tsconfig.json and print it out, which can be really useful for diagnosing configuration issues in general.

Object.defineProperty declarations

When writing in JavaScript files using allowJs, the new TypeScript 3.2 recognizes declarations that use Object.defineProperty. This means we get better completions, and stronger type-checking when enabling type-checking in JavaScript files.

// @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.

Do you know Create-React-App now supports typescript in her newest version? Now you do 😃

Error Messages get a Makeover

If you use typescript, you are witness to the fact that there is serious thought that goes into the whole error and error reporting and documentation experience. This new version still goes deeper into this with the following new stuff: