TypeScript has quite the fast release cycle so it’s easy to miss some of their nice features. Here are some of the things I missed when they came out.
Optional Chaining extra goodies (3.7)
JavaScript will get optional chaining soon so the Microsoft team implemented it in TS as well.
I’m sure you know the basics already:
1foo?.bar?.baz // returns undefined if foo or bar is nullish (null or undefined).
That means you can replace code that looks like this:
1if (foo && foo.bar && foo.bar.baz)
with this
1if (foo?.bar?.baz)
But there’s two more things that are easy to miss:
Optional Property Access
This is handy if you want to access a property of an optional array:
1const arr = [{ a: 1 }]2arr[0].a // ❌ this would throw if arr is not an array3arr?.[0].a // ✅ 😎
Optional Call
This does what you would expect, call the function if it’s a callable, else return undefined. This is useful if you have an optional function parameter in a function, for example.
1function f() {}2const g = null34f() // ✅5f?.() // ✅67g() // ❌ throws8g?.() // ✅ returns undefined
Assertion Functions (3.7)
This lets you write your own assertion functions you might know from Node and typecheck properly.
1// example function from docs2function assertIsDefined<T>(val: T): asserts val is NonNullable<T> {3 if (val === undefined || val === null) {4 throw new AssertionError(5 `Expected 'val' to be defined, but received ${val}`6 );7 }8}910const result: Array<Number> | null = await getStuffFromService()11result.map(...) // ❌ undefined is not a function12assertIsDefined(result)13result.map(...) // ✅
Const Assertions (3.4)
One thing tripping up new JavaScript developers is that the const
keyword doesn’t create a constant for reference types (like objects and arrays). Only the binding to the variable is not allowed. Let’s look at an example:
1const myObject = { a: 1 }2myObject = { d: 1 } // ❌ This is trying to bind the myObject variable to a new object and is not allowed34const myObject = { a: 1 }5myObject.b = 2 // ✅ This is allowed in JavaScript!
TypeScript actually shows an error here, since it infers the type as having exactly one attribute: a
.
TypeScript doesn’t complain when trying to change existing attributes, though:
1const myObject = { a: 1 }2myObject.a = 2 // ✅
The same is true for arrays:
1const myArray = [1, 2, 3] // inferred type is number[]2myArray.push(4) // ✅ allowed!
What if you want to have a real, read-only constant? Here the new const assertions
come to the rescue:
1const myObject = { a: 1 } as const // inferred type is now {readonly a: 1}2myObject.a = 2 // ❌ Error!34const myArray = [1, 2, 3] // inferred type is now readonly [1, 2, 3]5myArray.push(4) // ❌ Error!
The unknown type (3.0)
The new unknown
type is designed to be the type-safe version of the dreaded any
type. In particular, we can not run any operation on it without any type checking beforehand. An example:
1let value1: any2value1.a.b.c.d // ✅ allowed3value1.toString() // ✅ sure!4value1() // ✅ go ahead5new value1() // ✅ be my guest67let value2: unknown8value2.a // ❌ nope9value2.toString() // ❌ nope again10value2() // ❌ still nope11new value2() // ❌ nice try
When would this be useful? Think about working with results from API calls or de-serializing a value from localStorage
:
1const item = localStorage.getItem(someKey) // '1.2345'2const value: unknown = JSON.parse(item) // could be anything34value.toFixed(2) // ❌ not allowed. Would throw if item wasn't set in localStorage56if (typeof value === 'number') {7 value.toFixed(2) ✅// 1.238}
Conclusion
TypeScript gets better and better and sometimes it’s hard to keep up to date. I hope that this short write-up might be helpful for some of you.