With the release of TypeScript 3.7, some great new features that are included from ES2020 that are now part of TypeScript. The new features include things like optional chaining, nullish coalescing, check for uncalled functions, and more.
In this article, we’ll look at some of them in more detail.
Optional Chaining
Optional chaining is a feature that lets us get a deeply nested property of an object without worrying if any of the properties are null
or undefined
.
We can use the ?.
operator to get the properties of an object instead of using the usual dot operator. For example, instead of writing:
let x = a.b.c.d;
We write:
let x = a?.b?.c?.d;
With the optional chaining operator, we don’t have to worry about properties b
or c
being null or undefined, which saves us from writing lots of code to check is these properties exist.
If any of the intermediate properties are null or undefined, then undefined is returned instead of crashing the app with errors.
This means that we no longer have to write something like:
let x = a && a.b && a.b.c && a.b.c.d;
To get the d
property from the a
object.
One thing to be careful is that if strictNullChecks
flag is on, then we’ll get errors if we operate on an expression with the optional chaining operator inside a function with a parameter that has optional parameter as the operand.
For example, if we write:
let a = { b: { c: { d: 100 } } };
const divide = (a?: { b: { c: { d: 100 } } }) => {
return a?.b?.c?.d / 100;
}
Then we get the error ‘Object is possibly ‘undefined’.(2532)’.
Nullish Coalescing
The nullish coalescing operator lets us assign a default value to something when something is null or undefined.
An expression with the nullish coalescing looks like:
let x = foo ?? bar;
Where the ??
is the nullish coalescing operator.
For example, we can use it as follows:
let x = null;
let y = x ?? 0;
console.log(y); // 0
Then we get 0 as the value of y
.
This is handy because the alternative was to use the ||
operator for assigning default values. Any falsy value on the left operand would cause the default value on the right operand to be assigned, which we might not always want.
The nullish coalescing operator only assigns the default value when the left operand is null or undefined.
For example:
let x = 0;
let y = x || 0.5;
In the code above, 0 is a valid value that can be assigned to y
, but we still assign the default value of 0.5 to it because 0 is falsy, which we don’t want.
Assertion Functions
TypeScript 3.7 comes with the asserts
keyword which let us write our own functions to run some checks on our data and throws an error if the check fails.
For example, we can write the following to check if a parameter passed into our assertion function is a number:
function assertIsNumber(x: any): asserts x is number {
if (typeof x === 'number') {
throw new Error('x is not a number');
}
}
assertIsNumber('1');
assertIsNumber(1);
When we run the code above, we should get ‘Uncaught Error: x is not a number’.
In the code above, the asserts
keyword checks whatever condition comes after it.
It’s a function that returns void
which means that it doesn’t return anything. It can only throw errors if it doesn’t meet the condition we define.
Better Support for Functions that Return the Never Type
With TypeScript 3.7, now the TypeScript compiler recognizes functions that returns the never
type is run in a function that returns some other type.
For example, before TypeScript 3.7, we have to write the following to avoid an error:
const neverFn = (): never => {
throw new Error();
};
const foo = (x: string | number): number => {
if (typeof x === 'string') {
return +x;
}
else if (typeof x === 'number') {
return x;
}
return neverFn();
}
The code above would get us the error “Function lacks ending return statement and return type does not include ‘undefined’.” Because we did add return
before the neverFn()
function call.
TypeScript versions earlier than 3.7 don’t recognize the never function as a valid path because it doesn’t allow a path in the code that returns undefined
if a return type is specified.
Now removing the return
in return neverFn();
will work if TypeScript 3.7 is used to compile the code.
Allowing Some Recursive Type Aliases
Type aliases that aren’t assigned to itself are now allowed with TypeScript 3.7. For example, the following is still not allowed:
type Bar = Bar;
since it just replaces the Bar
type with itself forever.
If we try to compile the code above, we would get the error “Type alias ‘Bar’ circularly references itself. (2456)“.
However, now we can write something like:
interface Foo { };
interface Baz { };
type Bar = Foo | Baz | Bar[];
This is because the Bar[]
type isn’t directly replacing Bar
, so this type of recursion is allowed.
Generating Declaration Files when AllowJs Flag is On
Before TypeScript 3.7, we can’t generate declaration files when the --allowJs
flag is on, so TypeScript code that is compiled with JavaScript can’t have any declaration files generated.
This means that type checking can’t be done with JavaScript files that are being compiled, even if they have JSDoc declarations.
With this change, now type checking can be done with those JavaScript files.
Now we can write libraries with JSDoc annotated JavaScript and support TypeScript users with the same code.
The TypeScript compiler since 3.7 will infer the types of the JavaScript code from the JSDoc comments.
Uncalled Function Checks
Forgetting to call a function by omitting the parentheses is a problem that sometimes causes bugs. For example, if we write the following:
const foo = () => { };
const bar = () => {
if (foo) {
return true;
}
}
We’ll get the error “This condition will always return true since the function is always defined. Did you mean to call it instead?(2774)” when we write the code above and try to compile it with the TypeScript 3.7 compiler.
This code wouldn’t give any errors before version 3.7.
Conclusion
As we can see, TypeScript 3.7 gives us a lot of useful new features that aren’t available before. Optional chaining and nullish coaslecing operators are handy for avoiding null or undefined errors.
Recognizing function calls that return the never
type is also handy for writing code with paths that don’t always return.
Having recursive type aliases help with combining some kinds of types into one alias.
For developers that write libraries, TypeScript 3.7 can infer types from JavaScript files that are compiled with the TypeScript compiler by checking the JSDoc comments.
Finally, uncalled functions are now checked by the TypeScript compiler if they’re written like we’re trying to access it as a property.