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.

Lotanna Nwose
Bits and Pieces

--

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

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.jsons 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 thepackage.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-checklet 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:

  1. Better missing property errors (and cleaner missing attributes in JSX)
  2. Better error spans in arrays and arrow functions
  3. Error on most-overlapping types in unions (a.k.a. “pick most overlappy type”)
  4. Related spans when a typed this is shadowed
  5. “Did you forget a semicolon?” on parenthesized expressions on the next line
  6. More specific messages when assigning to const/readonly bindings
  7. More accurate message when extending complex types
  8. Use relative module names in error messagesBreaking changes and deprecations

lib.d.ts changes

TypeScript has recently moved more to generating DOM declarations in lib.d.ts by leveraging IDL files provided by standards groups. Upgraders should note take note of any issues they encounter related to the DOM and report them.

More specific types

Certain parameters no longer accept null, or now accept more specific types as per the corresponding specifications that describe the DOM.

More platform-specific deprecations

Certain properties that are WebKit-specific have been deprecated. They are likely to be removed in a new version.

wheelDelta and friends have been removed.

wheelDeltaX, wheelDelta, and wheelDeltaZ have all been removed as they are deprecated properties on WheelEvents.

As a solution, you can use deltaX, deltaY, and deltaZ instead. If older runtimes are a concern, you can include a file called legacy.d.ts in your project and write the following in it:

// legacy.d.tsinterface WheelEvent {
readonly wheelDelta: number;
readonly wheelDeltaX: number;
readonly wheelDeltaZ: number;
}

JSX resolution changes

The logic used for resolving JSX invocations has been unified with the logic for resolving function calls. While this has simplified the compiler codebase and improved some use-cases, there may be some differences which we may need to reconcile. These changes are likely unintentional so they are not breaking changes, but upgraders should note take note of any issues they encounter and report them.

Important Need-to-Knows

Here we would look at some caveats that ships with some new changes in this new typescript version:

  1. The use of strictBindCallApply might uncover previously unreported errors so it is a a breaking change in the strict mode.

2. The strictBindCallApply cannot fully model functions with overloads, in any case, the last overload would be the only one modeled.

3. Bigint is only available for the esnext target

Installing Typescript

Easily install with this line of npm command:

npm install -g typescript

Conclusion

We have seen the new typescript release and according to the roadmap here the next release is in 2 months time so wait for it. Remember you get massive typescript support if you use VS Code (do you use VS Code?). What is your favorite new addition to typescript? Let me know in the comments.

--

--

Helping Startups with Webhooks management at Convoy so they can focus on their core product offerings. Twitter:@viclotana