New Features in TypeScript You Didn’t Know Exist

A quick overview of the most exciting new features in TypeScript 3 that you might have missed!

Introduction

TypeScript is an open-source, strongly-typed, object-oriented compiled language developed and maintained by Microsoft, it is a superset of the very popular JavaScript that was built to bring static types to modern JavaScript. The TypeScript compiler reads in TypeScript code, which has things like type declarations and type-annotations and emits a clean and readable JavaScript with those constructs transformed and removed. That code runs in any ECMAScript runtime like your favorite browsers and Node.js.

At its core, this experience means analyzing your code to catch things like bugs and typos before your users run into them; but it brings more than that — Daniel Rosenwasser

In this post, you will be introduced to a few amazing features that has been shipped with TypeScript in the various 3.x versions. TypeScript is currently in version 3.4 and the next version is set to be released later this month.

TypeScript versions:

These new features will be explained in chronological order:

A free tip: Use Bit (github) to easily manage, share and reuse your JS/ TS components. Modularity and reusability are key factors to a better and more sustainable code!

Use Bit to share your components (an example)

Project References

This new concept was shipped with TypeScript 3.0, it basically lets one TypeScript project to depend on another TypeScript project by referencing the tsconfig.json files. This encourages a more obvious modular way of writing code. TypeScript 3.0 also introduces a new mode for tsc, the --build flag, it works seamlessly with project references to cause faster builds.

New unknown top type

A new top type was also introduced in TypeScript 3.0, the unknown type. It is just like the any type but it type-safe. In usage, anything can be assigned the unknown type but the unknown type is not assignable to anything but itself and any without a type assertion or a control flow based narrowing.

defaultProps support in JSX

For React development mostly in JSX, TypeScript 3.0 shipped with support for a new type alias in the JSX namespace called LibraryManagedAttributes . It is a helper type that defines changes on the prop types of components before use. This now allows for modifications around provided and inferred props and mappings.

export 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 />

It is important to note however that default properties are inferred from the defaultProps property type so if an explicit type annotation is added, the compiler will not be able to identify default properties.

Mapped types on tuples and arrays

In TypeScript 3.1, rather than introduce a new concept for mapping over a tuple, mapped object types now just works as it should when iterating over tuples and arrays. This means that if you are already using existing mapped types like Partial or Required from lib.d.ts , they automatically work on tuples and arrays now. This makes TypeScript better-equipped to express functions similar to Promise.all .

type 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>] .

Version selection

This is a very exciting feature that shipped with the TypeScript 3.1 version, a way for both the developer and the compiler to use new features and keep track of versions in use at the same time. 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:

{

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

strictBindCallApply

JavaScript has the bind , call , and apply methods used on functions that allow us to do things like bind this and partially apply arguments, call functions with a different value for this , and call functions with an array for their arguments. The TypeScript team took a while to model this functions and they all initially took any number of arguments and returned any.

In TypeScript 3.1, they took the parameter types and combined it with the the concept of modelling parameter lists with tuple types to now ensure our uses of bind , call , and apply are more strictly checked when we use a new flag called strictBindCallApply . When using this new flag, the methods on callable objects are described by a new global type called CallableFunction which declares stricter versions of the signatures for bind , call , and apply .

Like this:

function 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

So, whether you do any sophisticated meta-programming, or you use simple patterns like binding methods in your class instances, this feature can help catch a lot of bugs.

Supporting BigInt

BigInt is a built-in object that provides a way to represent whole numbers larger than 2 to power 53, which is the largest number JavaScript can reliably represent with the Number primitive. TypeScript 3.2 brings type-checking for BigInts, as well as support for emitting BigInt literals when targeting esnext .

The syntax in TypeScript for the new primitive type called the bigint . 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:

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)

tsconfig.json inheritance through Node.js packages

In TypeScript 3.2 , tsconfig.json can now be resolved 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 will climb up node_modules folders looking for a @my-team/tsconfig-base package. In each of those packages, TypeScript first checks whether package.json contains a "tsconfig" field, and then try to load a configuration file from that field. If none 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. Imagine how useful this is for very large projects.

const assertions

TypeScript 3.4 ships a new construct for literal values called const assertions. The 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 arrays are readonly tuples, or that object literals get readonly properties.

// 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;

Type-checking for globalThis

The TypeScript team also worked on the difficulty of trying to access values in the global scope, the new TypeScript 3.4 introduces support for type-checking ECMAScript’s new globalThis — a global variable that points to the global scope. Unlike other solutions, globalThis provides a standard way for accessing the global scope which can be used across different environments.

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

Conclusion

You have been introduced to most of the newest features of Typescript all the way from version 3.0, the next TypeScript version is going to be released in a couple of weeks according to the official roadmap. All the breaking changes can be viewed here. What is your favorite feature?