TypeScript 3 - What you might have missed
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:
foo?.bar?.baz // returns undefined if foo or bar is nullish (null or undefined).
That means you can replace code that looks like this:
if (foo && foo.bar && foo.bar.baz)
with this
if (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:
const arr = [{ a: 1 }]
arr[0].a // ❌ this would throw if arr is not an array
arr?.[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.
function f() {}
const g = null
f() // ✅
f?.() // ✅
g() // ❌ throws
g?.() // ✅ returns undefined
Assertion Functions (3.7)
This lets you write your own assertion functions you might know from Node and typecheck properly.
// example function from docs
function assertIsDefined<T>(val: T): asserts val is NonNullable<T> {
if (val === undefined || val === null) {
throw new AssertionError(
`Expected 'val' to be defined, but received ${val}`
);
}
}
const result: Array<Number> | null = await getStuffFromService()
result.map(...) // ❌ undefined is not a function
assertIsDefined(result)
result.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:
const myObject = { a: 1 }
myObject = { d: 1 } // ❌ This is trying to bind the myObject variable to a new object and is not allowed
const myObject = { a: 1 }
myObject.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:
const myObject = { a: 1 }
myObject.a = 2 // ✅
The same is true for arrays:
const myArray = [1, 2, 3] // inferred type is number[]
myArray.push(4) // ✅ allowed!
What if you want to have a real, read-only constant? Here the new const assertions
come to the rescue:
const myObject = { a: 1 } as const // inferred type is now {readonly a: 1}
myObject.a = 2 // ❌ Error!
const myArray = [1, 2, 3] // inferred type is now readonly [1, 2, 3]
myArray.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:
let value1: any
value1.a.b.c.d // ✅ allowed
value1.toString() // ✅ sure!
value1() // ✅ go ahead
new value1() // ✅ be my guest
let value2: unknown
value2.a // ❌ nope
value2.toString() // ❌ nope again
value2() // ❌ still nope
new value2() // ❌ nice try
When would this be useful? Think about working with results from API calls or de-serializing a value from localStorage
:
const item = localStorage.getItem(someKey) // '1.2345'
const value: unknown = JSON.parse(item) // could be anything
value.toFixed(2) // ❌ not allowed. Would throw if item wasn't set in localStorage
if (typeof value === 'number') {
value.toFixed(2) ✅// 1.23
}
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.