TypeScript is improving every day. We keep getting new features with every release. In this article, we’ll look at the new stuff that’s released with TypeScript 3.4.
New features include better type inference for higher-order generic functions. changes to readonly
types and faster builds with the --increment
flag, and more.
New Features in TypeScript 3.4
–incremental Flag
To speed up builds after the first build, the --incremental
flag of the TypeScript compiler will let us build only based on what’s changed.
We can add the option to tsconfig.json
of our project to get this feature, under the compilerOptions
section, as follows:
{
"compilerOptions": {
"incremental": true,
"outDir": "./lib"
},
"include": ["./src"]
}
It works by looking for the .tsbuildinfo
which is created with the first build. If it doesn’t exist, then it’ll be generated. It’ll use this file to know what has been built and what has not.
It can be safely deleted and not impact our build. We can name the file with a different name by adding a tsBuildInfoFile
option to the compilerOptions
section tsconfig.json
as follows:
{
"compilerOptions": {
"incremental": true,
"tsBuildInfoFile": "./front-end-app",
"outDir": "./lib"
},
"include": ["./src"]
}
For composite projects, which has composite
flag set to true
in tsconfig.json
, references between different projects can also be built incrementally. These projects will always generate a .tsbuildinfo
files.
When the outFile
option is used, then the build information file name will be based on the output file’s name. For example, if the output file is foo.js
then the build information file will be foo.tsbuildinfo
.
Higher-Order Type Inference in Generic Functions
When we have functions that take other functions as parameters, we’ll get type inference for the types of functions that are passed in and returned.
For example, if we have a function that composes multiple functions to return a new function as follows:
function compose<A, B, C, D>(
f: (arg: A) => B,
g: (arg: B) => C,
h: (arg: C) => D
): (arg: A) => D {
return x => h(g(f(x)));
}
When we fill in the types for the generic markers as follows:
function compose<A, B, C, D>(
f: (arg: A) => B,
g: (arg: B) => C,
h: (arg: C) => D
): (arg: A) => D {
return x => h(g(f(x)));
}
interface Employee {
name: string;
}
const getName = (employee) => employee.name;
const splitString = (name) => name.split('');
const getLength = (name) => name.length;
const fn = compose(getName, splitString, getLength)
Then we can call the fn
function by writing:
const len: number = fn(<Employee>{ name: 'Joe' });
TypeScript 3.4 or later is smart enough to go through the chain of function calls and infer the types of each function automatically and the return type of the function returned from compose
.
It can infer that fn
returns a number.
TypeScript versions older than 3.4 will infer the empty object type and we get errors with the assignment expression above.
ReadonlyArray
and readonly
tuples
Using read-only array types is now easier with TypeScript 3.4. We can now declare a read-only array with the readonly
keyword.
For example, if we want a read-only string array, we can write:
const strArr: readonly string[] = ['a', 'b', 'c'];
Now we have an array that we can’t push to, change entries or anything else that modifies the array.
This is much more compact compared to the ReadOnlyArray<string>
type.
With TypeScript 3.4, we have the new read-only tuple type. We can declare a read-only tuple as follows:
const strTuple: readonly [string, string] = ['foo', 'bar'];
The readonly
modifier on mapped types will convert to array-like types to their corresponding readonly
counterparts.
For example, if we have the type:
type ReadOnly<T> = {
readonly [K in keyof T]: T[K]
}
Then when we pass in a type into the generic type placeholder of Readonly
as follows:
type foo = Readonly<{foo: number, bar: string}>;
We get that the foo
type is:
type foo = {
readonly foo: number;
readonly bar: string;
}
As we can see, both fields have become readonly
, which isn’t the case before TypeScript 3.4.
We can also use mapped types to remove the readonly
modifier from all the fields. To do this, we add a -
before the readonly
modifier.
For example, we can write:
type Writable<T> = {
-readonly [K in keyof T]: T[K]
}
interface Foo{
readonly foo: string;
readonly bar: number;
}
type foo = Writable<Foo>;
Then we get:
type WriteFoo = {
foo: string;
bar: number;
}
For the type foo
.
The readonly
modifier can only be used for syntax on array types and tuple types. It can’t be used on anything else.
Photo by Erin Wilson on Unsplash
Const Assertions
A constructor called const
assertions is introduced with TypeScript 3.4. When we use it, we signal that literal types can’t change to a type that’s wider in scope, like going from 1
to string
. Objects literals get readonly
properties. Array literals become readonly
tuples.
For example, the following is valid:
let x: 'foo' = "foo" as const;
We get that x
is type 'foo'
when we inspect its type.
Another example would be a number array:
let x = [1, 2] as const;
When we hover over x
, we get that the type is readonly [1, 2]
.
Conclusion
With TypeScript 3.4, we have multiple changes for read-only types, including using the readonly
keyword to declare read-only arrays and tuples.
Also, we can add and remove the readonly
modifier with mapped types with the readonly
and -readonly
modifiers before the index signature or field name.
The const
assertion is for converting a value into a read-only entity.
High order generic functions that let us compose multiple functions together to return a new composed function also have smarter type inference than in earlier versions.
Finally, we have the --incremental
flag to create incremental builds, which makes code build faster on subsequent builds.