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:
- 3.0 released July 2018
- 3.1 released September 2018
- 3.2 released November 2018
- 3.3 released January 2019
- 3.4 released March 2019
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!
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 thebind
, 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?