Typescript Tip 01: Using the “in” Keyword to Deal with Multiple Types
Today we’re talking about Type Narrowing — special checks and assignments that allow typescript to infer more specific types than declared — and how you can use the in keyword to distinguish between several types.
What is Type Narrowing?
Imagine we have a simple function called padLeft
which adds padding to the left of the given input
. There are two cases our padLeft
function needs to handle
- If
padding
is a number, it will treat that as the number of spaces we want to prepend to input. - If
padding
is a string, it should just prepend padding to input.
Below is an implementation of padLeft
, which illustrates type narrowing.
function padLeft(padding: number | string, input: string) {
if (typeof padding === "number") {
return " ".repeat(padding) + input;
}
return padding + input;
}
It may not seem like much, but TypeScript is doing something pretty clever here. Within our if check, TypeScript sees typeof padding === “number”
and understands that as a particular form of code called a type guard. TypeScript uses the type guard to infer more specific types for the padding variable.
In the example above, TypeScript used type narrowing and inferred:
(parameter) padding: number
on line 3(parameter) padding: string
on line 5
The Problem We Are Trying to Solve
Take a look at the code below, there are two typescript errors ❌ because TypeScript is not able to narrow types between A
and B
. We’ll see how we can update the code in the section below and resolve these typescript errors.
interface A {
x: string
doSomething: () => boolean;
}interface B {
x: string
doAnotherThing: () => boolean;
}// FAIL
function example(value: A | B) {
// ❌ Property 'doSomething' does not exist on type 'A | B'
if (value.doSomething && value.doSomething()) {
//....
}
// ❌ Property 'doAnotherThing' does not exist on type 'A | B'
if (value.doAnotherThing && value.doAnotherThing()) {
//...
}
}
Using the “in"
keyword
JavaScript has an operator for determining if an object has a property with a name: the in
operator. TypeScript takes this into account as a way to narrow down potential types. Let’s revisit the example above and update our code to use the in
operator.
// PASS
function example(value: A | B) {
if ('doSomething' in value && value.doSomething()) { // ✅ CODE UPDATED
//...
}
if ('doAnotherThing' in value && value.doAnotherThing()) { // ✅ CODE UPDATED
//...
}
}
Similar to the typeof
operator we saw being used in the first section, the conditional check 'doSomething' in value
narrows the type of value
to A, and the conditional check 'doAnotherThing' in value
narrows the type of value
to B
.
👋 That’s all for this week; try solving the problems below if you fancy a challenge. Otherwise, come back next week for another tip!
Challenge for the Reader
Remove all the TypeScript errors in my custom TS Playground to test your knowledge of Type Narrowing.
Additional Resources
We only covered some of the many ways you can narrow types in TypeScript. Check out https://www.typescriptlang.org/docs/handbook/2/narrowing.html if you’re interested in learning more about them.