TypeScript 4.0 is a major milestone in the TypeScript programming language and has currently leapfrogged 3.9 to become the latest stable version. In this post, we’ll look at the new features TypeScript 4.0 offers.
To get started using 4.0, you can install it through NuGet or via NPM:
npm i typescript
You can test the code using the TypeScript playground or a text editor that supports TypeScript. I recommend using Visual Studio Code, you can get set up instructions here.
In a nutshell, we can say TypeScript is strongly typed JavaScript. This means that it requires developers to accurately specify the format of their data types, consequently, it allows the compiler to catch type errors at compile time and therefore, give a better developer experience.
This process of accurately specifying the format of data types is known as type declaration or type definitions — it is also called typings or simple types.
With this feature, TypeScript gives types to higher-order functions such as curry
, concat
, and apply
. These are functions that take a variable number of parameters.
Consider a small contrived example of the concat
function below:
function simpleConcat(arr1, arr2) { return [...arr1, ...arr2]; } console.log(simpleConcat([1,2,3], [5,6])) // [1, 2, 3, 5, 6]
There is currently no easy way to type this in TypeScript. The only typing strategy available currently is to write overloads.
Function or method overloading refers to a feature in TypeScript that allows us to create multiple functions having the same name but a different number of parameters or types.
Consider this:
function concat1<T>(arr1: [T], arr2: []): [T] { return [...arr1, ...arr2] } function concat2<T1, T2>(arr1: [T1, T2], arr2: []): [T1, T2] { return [...arr1, ...arr2] }; function concat6<T1, T2, T3, T4, T5, T6>(arr1: [T1, T2, T3, T4, T5, T6], arr2: []): [T1, T2, T3, T4, T5, T6] { return [...arr1, ...arr2] } function concat7<T1, T2, T3, T4, T5, T6, A1, A2, A3, A4>(arr1: [T1, T2, T3, T4, T5, T6], arr2: [A1, A2, A3, A4]): [T1, T2, T3, T4, T5, T6, A1, A2, A3, A4] { return [...arr1, ...arr2] } console.log("concated 1", concat1([1], [])) console.log("concated 2", concat2([1,2], [])) console.log("concated 6", concat6([1,2,3,4,5,6], [])) console.log("concated 10", concat10([1,2,3,4,5,6], [10, 11, 12, 13]))
From the example above we can see that the number of overloads increases as the number of items in the array increases which is suboptimal. In concat6
we had to write 6 overloads even when the second array is empty and this quickly grew 10 overloads in concat10
when the second array had just 4 items.
Also, we can only get correct types for as many overloads as we write.
TypeScript 4.0 comes with significant inference improvements. It allows spread elements in tuple types to be generic and to occur anywhere in the tuple.
In older versions, REST element must be last in a tuple type. And TypeScript would throw an error if this were not the case:
// Tuple speard items are generic function concatNumbers<T extends Number[]>(arr: readonly [Number, ...T]) { // return something } // spread occuring anywhere in the tuble valid in 4.0 beta. type Name = [string, string]; type ID = [number, number]; type DevTuples = [...Name, ...Numbers]
Given these two additions, we can write a better function signature for our concat
function:
type Arr = readonly any[]; function typedConcat<T extends Arr, U extends Arr>(arr1: T, arr2: U): [...T, ...U] { return [...arr1, ...arr2]; } console.log("concated", typedConcat([1,2,3,4,5], [66,77,88,99]))
This is a pithy addition to TypeScript aimed at improving code readability.
Consider the code below:
type Period = [Date, Date]; // Example 1 older version type Period = [StartDate: Date, EndDate: Date]; // Example 2 4.0 beta function getAge(): [birthDay: Date, today: Date] { // ... }
Previously, TypeScript developers use comments to describe tuples because the types themselves (date, number, string) don’t adequately describe what the elements represent.
From our small contrived example above, “example 2” is way more readable because of the labels added to the tuples.
When labelling tuples all the items in the tuples must be labelled.
Consider the code below:
type Period = [startDate: Date, Date]; // incorrect type Period = [StartDate: Date, EndDate: Date]; // correct
In TypeScript 4.0, we can now use control flow analysis to determine the types of properties in classes when noImplicitAny
is enabled. Let’s elaborate on this with some code samples.
Consider the code below:
// Compile with --noImplicitAny class CalArea { Square; // string | number constructor(area: boolean, length: number, breadth: number) { if (!area) { this.Square = "No area available"; } else { this.Square = length * breadth; } } }
Previously, the code above would not compile if noImplicitAny
is enabled. This is because property types are only inferred from direct initializations, so their types must either be defined explicitly or using an initial initializer.
However, TypeScript 4.0 can use control flow analysis of this.Square
assignments in constructors to determine the types of Square
.
Currently, in JavaScript, a lot of binary operators can be combined with the assignment operator to form a compound assignment operator. These operators perform the operation of the binary operator on both operands and assigned the value to the left operand:
// compound operators foo += bar // foo = foo + bar foo -= bar // foo = foo - bar foo *= bar // foo = foo * bar foo /= bar // foo = foo/bar foo %= bar // foo = foo % bar
The list goes on but with three exceptions:
|| // logical or operator && // logical and operator ?? // nullish coalescing operator
TypeScript 4.0 beta would allow us to combine these three with the assignment operator thus forming three new compound operators:
x ||= y // x || (x = y) x &&= y // x && (x = y) x ??= y // x ?? (x = y )
unknown
on catch
clause bindingsPreviously, when we use the try … catch
statement in TypeScript, the catch clause is always typed as any
, consequently, our error-handling code lacks any type-safety which should prevent invalid operations. I will elaborate with some code samples below:
try { // ... }catch(error) { error.message error.toUpperCase() error.toFixed() // ... }
From the code above we can see that we are allowed to do anything we want — which is really what we don’t want.
TypeScript 4.0 aims to resolve this by allowing us to set the type of the catch variable as unknown
. This is safer because it’s meant to remind us to do a manual type checking in our code:
try { // ... }catch(error: unknown) { if(typeof error === "String") { error.toUpperCase() } if(typeof error === "number") { error.toFixed() } // ... }
TypeScript already supports jsxFactory
compiler option, this feature, however, adds a new compiler option known as jsxFragmentFactory
which enables users to customize the React.js fragment factory in the tsconfig.json
:
{ "compilerOptions": { "target": "esnext", "module": "commonjs", "jsx": "react", // React jsx compiler option "jsxFactory": "createElement", // transforms jsx using createElement "jsxFragmentFactory": "Fragment" // transforms jsx using Fragment } }
The above tsconfig.json
configuration transforms JSX
in a way that is compatible with React thus a JSX
snippet such as <article />
would be transformed with createElement
instead of React.createElement
. Also, it tells TypeScript to use Fragment
instead of React.Fragment
for JSX
transformation.
TypeScript 4.0 also features great performance improvements in --build
mode scenarios and also allows us to use the --noEmit
flag while still leveraging --incremental
compiles. This was possible in older versions.
In addition, there are several editor improvements such as @deprecated
JSDoc annotations recognition, smarter auto-imports, partial editing mode at startup (which aimed to speed up startup time).
TypeScript 4.0 was released on Aug 18 and all these exciting features and improvements where rolled-out with it. Without a doubt, these will improve both the developer experience and efficiency of using TypeScript. You can find more details on 4.0 here.
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Would you be interested in joining LogRocket's developer community?
Join LogRocket’s Content Advisory Board. You’ll help inform the type of content we create and get access to exclusive meetups, social accreditation, and swag.
Sign up nowCompare Prisma and Drizzle ORMs to learn their differences, strengths, and weaknesses for data access and migrations.
It’s easy for devs to default to JavaScript to fix every problem. Let’s use the RoLP to find simpler alternatives with HTML and CSS.
Learn how to manage memory leaks in Rust, avoid unsafe behavior, and use tools like weak references to ensure efficient programs.
Bypass anti-bot measures in Node.js with curl-impersonate. Learn how it mimics browsers to overcome bot detection for web scraping.