TypeScript 5+ Fundamentals, v4

Union Type Control Flow

TypeScript 5+ Fundamentals, v4

Check out a free preview of the full TypeScript 5+ Fundamentals, v4 course

The "Union Type Control Flow" Lesson is part of the full, TypeScript 5+ Fundamentals, v4 course featured in this preview video. Here's what you'd learn in this lesson:

Mike discusses how union types are commonly used in programs to represent different possibilities based on control flow. The concept of discriminated unions, where a literal type is used as a discriminator to determine the type of the larger value is also introduced in this segment.


Transcript from the "Union Type Control Flow" Lesson

>> So we can create that union type as we did before, right? We're using the or operator here, I explained the casting a little bit. You're going to see union types a lot in your programs. And the main reason for that has to do with control flow. So we talked a little bit about explicitly typing function return values.

But anytime you have a branch like this, where we have a coin flip and it's either heads or it's tails, those are two different branches within your function, right, there's an if-else. If we wanted to represent this as, not just returning a string like this, but we could say, this returns heads or tails, there you go.

So this is like, when thinking about union and intersection types, it's so much more common to see these union types. I expect in your day-to-day use of TypeScript, it's gonna be at least like a 100 to 1 ratio. These are overwhelmingly the more common thing. And it just has to do with control flow and different possibilities happening, depending on which branch of code you end up going down.

So here's a more complicated example, so here's heads or tails. In fact, we could do this more, aah, I'll leave it the way it is. You could also do this just to see like this. This would let it infer that it's not just a string, it's the string tails.

So here's the outcome of a coin flip. Now, let's look at a slightly more complicated example, and this is gonna build on what we saw in the last chapter with tuples. Remember, I told you I use tuples of size 2 all the time in my programs? Here's an example.

So I, let's say, I have some random processing task like, let's say, adding a user to my address book, and maybe this involves an API call or something. So I've got a success and a fail example, a successful result and a failing result. The first element of the tupple is the string success, or the string error.

And then depending on which case I'm dealing with, we could end up with this object that has name and email. Or we could end up with, literally, like an instance of the error class, something that might be thrown. So let's define a function called maybeGetUserInfo. Which flips a coin and if we get heads, it'll return success, if we get tails, it will return the failure result.

And we can see in the outcome here, we've got, here's the success type. There's that tupple, here's the string, here's the object. And then here's the failing case, it's error, and then here's my instance of error, that's the type error. So, Great, we can represent this as a tuple.

But, Outcome2., All right, so I've got my array here, I've got a 0 position and a 1 position, so it knows I've got two elements there. Let's reach for number 1. What do we got there? Literally, just the property name. That's all I can access on this thing, name.

And that's because while I have a name here, right, errors also have a property called name. And if we think back to our Venn diagram here, right, remember we said a union type allows for any of these values to be, whoops, to be present, that's perfect. But in terms of what assumptions we can make, how can we treat this?

Well, we can't be sure it's an error and we can't be sure it's that object with the property name and email, so you kind of get the lowest common denominator. You get anything that's on both and that's just name. So it's a very accepting type, but it's not very useful in its current form.

And so we need to learn how to work with types like this. Let's introduce a bunch of functions here. So we've got print even, which only takes even, right? This takes 2,468, that's the only thing allowed here. We've got print lowNum, which only takes a digit OneThroughFive. We've got printEvenNumberUnder5, which literally, the only thing this will be happy with is 2 or 4, very restricted function.

And then we've got printNumber, it'll take any number. So that'll work with everything we've been discussing so far, as well as all of the doubles in between those nice round numbers, and all of the other doubles that you can create in JavaScript. So, all right, let's create our value x, which is, I've reversed the orders of these now, right?

So now look at how the type is serialized on the screen. It's 2, 4, 6, 8, and then the missing things from 1, 3, and 5. So there you see evidence. It's sort of like one by one, putting these in. So let's look at what this type, evenOrLowNumber, what does it accept?

In fact, I'll leave this commented out because it already exists somewhere, In a higher-up example. So it accepts an even, it accepts a lowNumber, it accepts an evenOrLowNumber, very accepting. But when we try to pass it into these functions, well, it's not guaranteed to be even. And look at this.

It says, in our error, one is not assignable to type evens. It's almost like, it's going through the set and it's like, 2, you're fine, 4, you're fine, 6 and 8, both fine, 1, problem. And then it bails. That's enough for it to know this will not type check.

PrintLowNumber, Well, it's okay with 2 and 4, but once it gets to 6, bails. It says, this is not gonna work. Just the fact that you have a 6 in there, I don't even care about the rest of the set. I already know you're not going to type check.

PrintEvenNumberUnder5, all right, 2 and 4 are fine, 6 is where it hits the problem. And then finally, printNumber, It's fine there. It looks through every element of the set and says, looks like y'all are numbers, I'm fine with that.
>> When you say heads and tails is a return type, will e be a valid return?

>> Well, what be a valid return?
>> E.
>> E?
>> String error, I think you
>> Heads and tails are a valid return type. So, let me see if I can answer that question. If I've said, I'm gonna return heads or tails, and I do this I'm gonna get busted, literally nothing else other than the string heads and tails will do.

And TypeScript's even clever about trying to correct a little misspelling that I have here, like, did I mean tails? So, yeah, that one, I can answer, literally, nothing other than this or this. Now, I could always return tails That's fine. I can say, there's some possibility that it could be heads.

Maybe I'm designing for the future where I will introduce heads as a concept, and I don't wanna break people who consume this function. They should prepare for the possibility that heads will exist, I could do that. But I certainly can't do anything, like, what we would say here is that, what we're actually returning is just tails, and the contract we have to live up to is heads or tails.

And so the set, the string tails, it fits within heads and tails, it is type equivalent. Next, we're going to talk about narrowing with type cards. Because we can see a union type allows us to accept a variety of different values. But as we saw earlier, when we were looking at the second element of our tuple up here, right, we only had this name property.

So we're gonna talk a little bit about how you can use a union type to do some more useful things. And really, it's about narrowing with type_guards. We mentioned type_guards a little bit earlier in the course. Remember, we used typeof something equals number, or it's not undefined, so we used typeof.

There's another built in JavaScript concept that is regarded as a type_guard by TypeScript, instanceof. So I've got my first and my second elements of this tuple. And just remind yourselves that this was, it's either this success or this fail case, right? We've got the string, and then a different value here, depending on the string.

So, if the second element of the tuple is an instanceof error, note that the type in this branch of code is error. So we're no longer in a place where we're saying, I can only access this name property, right? Now we get the full glory of everything that's on error, which is a stack.

Stack trace might be there, a message and a name. And then when we have this else block, well, what's left? What's left after error? It's this object. So you can kind of think of this like a pie chart if you want, where part of your pie is like this error type, part of your pie is this object with a name and an email property.

And when you have a conditional like this, you're almost taking slices of the pie out, chunks, and saying, okay, I'm gonna handle the possibility that this is an error. And then down here, you get everything that's left that you haven't handled specifically. And in this case, it's this object.

But if you had more union types here, right, if it's this object or another object or a date, you could use an instanceof, you can handle all of those specifically. And then what's left in your else block, you'll see just anything that hasn't been handled yet cuz that's the only way you get to the else.

Does that make sense? Good, okay, so this is a way you could use a type_guard. There's another thing you could do, and it's called a discriminated_union. It's a fancy name for a technique. So we're still dealing with that same tuple. We still have the first and the second element here, right?

The first element, if we look at it, we've got success or error. And the second element, we've got this object or error. But TypeScript actually has stored more information, it's not telling us. If we just hovered over these, we might assume that, well, the first could be success, and then the second could be an error.

So maybe we'll allow success in the first position and an error instance in the second position. But TypeScript knows that it's really this. You get this pair or the other pair, you don't mix and match. And we'll see that with discriminated_unions. So here, look at what we're doing?

We're saying, if the first is equal to error, look at the second. So it knows they're connected, it knows we're in that first situation, right, the error situation. And down here, what's left? A case where it's successful. So what we would say is, this is a discriminated_union because we're using the first element of the tuple as a discriminator.

We're using that as a way to, it's almost like a signal that we can use to sort of take a whole larger picture, a larger scenario. And we're using that first position as an indicator of what the whole tuple value must be, depending on the first element. It's almost like a label, right?

And as long as we don't mix, as long as every of these two scenarios up here, right, so we don't reuse success. What if we did this? It's not an effective discriminated_union now, cuz it's, first off, it's never gonna be success. I mean, it's never gonna be error, sorry.

And now we're, sorry Is that it? Yep, but we haven't narrowed it down to anything cuz that's for both success and failure. So that's how a discriminated_union works. You use you often a literal type or a union of literal types, right, to serve as an indication that you can use to make a decision about the larger value in and of itself.

Learn Straight from the Experts Who Shape the Modern Web

  • In-depth Courses
  • Industry Leading Experts
  • Learning Paths
  • Live Interactive Workshops
Get Unlimited Access Now