Implement a NonNullish<T> Conditional Type in TypeScript

Marius Schulz
InstructorMarius Schulz

Share this video with your friends

Send Tweet
Published a year ago
Updated a year ago

In this lesson, we're going to learn about conditional types, a powerful feature of TypeScript's type system. Conditional types let us express non-uniform type mappings, that is, type transformations that differ depending on a condition.

A conditional type describes a type relationship test and selects one of two possible types, depending on the outcome of that test. It always has the form T extends U ? X : Y, which makes conditional types look similar to conditional expressions in JavaScript.

We're going to use a conditional type to implement a NonNullish<T> helper type. This type is identical to the NonNullable<T> helper type which is defined in the core type declaration files that ship with the TypeScript compiler. Step by step, we're going to resolve an application of the NonNullish<T> type to understand how conditional types are being evaluated.

Additional Reading

Instructor: [0:00] Let's have a look at conditional types in TypeScript. Conditional types are definitely an advanced feature of the type system, but a very powerful one. A conditional type lets us express a type relationship test, and depending on the outcome of that relationship test, it produces one of two possible types.

[0:18] A conditional type always has the following structure. It first checks whether sum type T extends sum type U. If that's the case, the resulting type is X. Otherwise, the resulting type is Y. Notice that this syntax mirrors conditional expressions in JavaScript, or sometimes referred to as the ternary operator.

[0:40] We have some condition. If that condition is truthy, we produce some value. Otherwise, we produce another value. With the theory out of the way, let's jump in, and let's write our first conditional type. I want to write a type called non-nullish of T. What this type is supposed to do is remove the types null and undefined from the given type T.

[1:07] I'm going to start with the type relationship test. I'm going to say, if T extends the type null or undefined, then we want to produce type never. Otherwise, we want to produce type T. This type might look cryptic, so let's take a look at the different parts.

[1:27] First, we're declaring a type alias called non-nullish, and we're accepting a single type parameter called T. Then we have the type relationship test. Here, we're saying that, if T is assignable to type null or undefined, we want the resulting type to be never. If that's not the case, we want the resulting type to be T.

[1:49] I'm going to walk you through an example in a minute, and then you're going to see why we've chosen the never type here. Before I'm going to do that, let's have a look at some examples of this type in action. Here, I have five applications of the non-nullish type.

[2:04] In the first example, we're applying the non-nullish type to type string, and the resulting type is string. That's because, if we remove the types null and undefined from the type string, we're still left with a type string. In example B, we're removing the types null and undefined from the union type string or null, and the resulting type is string.

[2:27] The same is true for type C. We have the union type string or null or undefined, but when we remove null and undefined, we're left with string. In example D, the resulting type is never. That's because we're removing the types null and undefined from the type null, so the only thing we're left with is never.

[2:49] In the last example, we also get type never. We have the union type null or undefined here. We're removing both null and undefined, so what we're left with is type never. It turns out that this helper type is useful in a lot of TypeScript applications.

[3:05] This is why it's defined in one of the core type declaration files that ships with the TypeScript compiler, so we don't have to define it ourselves. I'm going to go in here, and instead of using non-nullish, we're going to use non-nullable everywhere. If I jump to the definition of the non-nullable type, you can see that it's defined in exactly the same way in the lib.es5.d.ts file.

[3:32] Let's have a look at a practical example now. Let's assume that we have a type called email recipient. It's defined as either a string, or an array of strings, or the types null or undefined. I'm also going to create another type called non-nullable email recipient.

[3:52] This type evaluates to the union type string or string array. This is because, when we remove null and undefined from this union type here, we're left with a type string or string array. Now, I want to walk you through how we can resolve this type step by step.

[4:08] The first thing I'm going to do is inline our email recipient type here. When we apply the non-nullable type to a union of types, that's equivalent to applying the non-nullable type to every type individually. What we get here is still a union type with four constituent types, but now we're applying the non-nullable type to all these individual types.

[4:30] We have non-nullable of type string, or non-nullable of type string array, or non-nullable of type null, or non-nullable of type undefined. Next up, I'm going to inline the definition of the non-nullable type. If I hover over that and I copy the definition, we can come back in here, and we can start inlining.

[4:53] If we apply the non-nullable type to the type string, what we get is this conditional type. We can do the same to the other types as well. Now, we can evaluate our conditional types, starting with the first one. The type string is not assignable to the union type null or undefined, so what we're left with here is the type string.

[5:22] The same is true for the next line. The type string array is not assignable to the type null or undefined, so again, we're left with the type string array.

[5:32] The type null, however, is assignable to the type null or undefined, so in this case, we're producing the type never. Similarly, the type undefined is also assignable to the union type null or undefined, so again, we produce never.

[5:49] Lastly, if we have the type never in a union type, we can simply remove that. This is because never represents values that can never occur, so we can remove that from a union type. Here we go. We're left with a union type string or string array, which we've seen in the beginning.