Imagine making an API request to an endpoint that provides data organized into folders. Requesting the data from a specific folder would return items that the folder contains as well as additional folders nested directly under the folder in question.
A folder navigation UI would need to split apart the
Folders from the
Items in order to render properly. This could easily be solved in a component by using
filter to get rid of the undesired items at the point of use but at the cost of iterating the set for each use.
Also, what if I needed to split other kinds of arrays? For example, what if I had a list of users with an
isActive property, and I wanted to split those into
[activeUsers, inactiveUsers]? The underlying problem is pretty general so a small utility function would help here.
Problem: Given an array of two kinds of things (
U), let’s split them apart and return each subsection of the array in a tuple (
The functions works, but the compiler can’t actually tell if the
splitFn is narrowing the types correctly so it complains. There are two primary ways to provide it additional context.
Solution 1: Type Assertions
The most obvious solution is just to tell the compiler what’s happening via a type assertion. These assertions are done using the
Generally, type assertions are discouraged as they can easily reduce the effectiveness of the compiler by overriding it erroneously. That’s true here as well: providing a
splitFn that doesn’t accurately or exhaustively split the types will cause problems downstream.
Solution 2: Type Predicates
In programming, a “predicate” is a function with a single parameter that returns either
false. Typescript uses predicates to narrow types. It’s annotated with a special return description using the
splitFn above is already functioning as predicate, so updating the type definition will clear the error and narrow the type for the compiler.
This clears the error as well, but there are a couple of caveats:
Caveat 1: This approach makes more sense when splitting apart an array into two strongly differentiated subtypes represented in the type system. In the second example (the active users) the following works, but the predicate function has a hard time describing the intent when narrowing within a type:
Here it would probably be better to create
InactiveUser types. However, certain subgroupings resist type descriptions. There’s no easy way that I know of to break apart a list of users into
oldUsers where the differentiator is a test for if an
accountCreated timestamp happens before or after a specific time.
Caveat 2: While the type safety here is stronger than in solution 1, there’s still no guarantee that the predicate is error-free in its differentiation logic. A poorly constructed predicate function could still result in compiler errors downstream if it fails to accurately differentiate between the types.