Narrow Down Types With Typescript Generic Constraints

Any statically-typed language supports generic, Typescript is no exception. With generic, we can write a flexible type signature that encourages reusability for our functions. Without it, functions are restricted to one specific data type which in turn, makes it hard to reuse.

In the example above, the function createArray accepts an array of string and outputs an array of string. Depending on the use case, but we can improve this function by giving it a generic type such that it accepts more than just string type.

With generic, we can enforce our createArray function to accept and return a specific type.

In addition, omitting the type will cause the createArray function to infer the type from the argument (['Life', 43] is translated into string | number). This looks great, our function can be reused with different type signatures.

However, a lot of times when writing a generic function, we might have some prior knowledge about how our function works underneath and therefore we can narrow down the type. In Typescript, this is called Generic Constraint.

Understanding Generic Constraint

A generic constraint is simply a way to put some constraints to a type. Suppose we have a generic function like this,

You’ll notice that null and undefined are allowed here, it might be what we wanted, but I'm sure most of the time these are invalid inputs. To solve this, we can put a constraint on our generic type to disallow empty value.

In the example above, T extends {} means that T can be any type that is a subclass of {} (an object), in Javascript string, number, array and object are all subclasses of object, while undefined and null are not, therefore they are disallowed. This is what generic constraint syntax look like, by extending the T type.

Generic Type With Specific Behavior

Somewhere in our function, we might invoke a specific method of the argument, but with generic, we can’t be sure such property exists. Therefore we need to further constraint our function to only accept an argument with a specific signature.

In the example above, only string and array have property .length while the rest are disallowed.

Get What You Ask For

Now that we’ve gained some ground, let’s see how we can perform a more advanced constraining with Typescript. Suppose we want to create a function that accepts a custom shape and return the exact same shape like this,

This is a perfect case for generic constraints. Let’s start by defining our custom type.

Our custom type has three fields: foo, bar, and baz. The argument can be a full set or a subset of CustomObject, to achieve this we can use the Typescript built-in type Partial.

Perfect! Our function returns exactly the shape we asked for no more and no less. Note that the empty string '' is simply a placeholder value to fulfill the object shape, it doesn't actually do anything (we can customize it though).

Alternatively, if you dislike the fact that we use an object to define the shape, we can also do it like this,

Which one is better, you decide.

Originally published at https://frendyguo.me.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store