1 00:00:00,180 --> 00:00:02,520 - Now we're gonna get to some relatively complex 2 00:00:02,520 --> 00:00:05,790 typescript stuff by talking about type predicates. 3 00:00:05,790 --> 00:00:07,320 Let's take a look at this code real quick 4 00:00:07,320 --> 00:00:09,300 for a simple example and then the next example 5 00:00:09,300 --> 00:00:11,610 I'll show you a little bit more of a real world example. 6 00:00:11,610 --> 00:00:13,710 So here we have a type of person and to-do 7 00:00:13,710 --> 00:00:16,410 the person has a name and the to-do has a title. 8 00:00:16,410 --> 00:00:18,000 Then I have a print function which is taken 9 00:00:18,000 --> 00:00:18,870 in an object that could 10 00:00:18,870 --> 00:00:21,330 either be a person or it could be a to-do. 11 00:00:21,330 --> 00:00:22,680 And then I'm just checking to figure out 12 00:00:22,680 --> 00:00:23,880 if they have a name property. 13 00:00:23,880 --> 00:00:25,050 Well that means they're a person. 14 00:00:25,050 --> 00:00:26,370 So I'm gonna log out the name 15 00:00:26,370 --> 00:00:28,650 otherwise I'm going to be logging out the title. 16 00:00:28,650 --> 00:00:30,713 As you can see, the object down here is the to-do type 17 00:00:30,713 --> 00:00:33,120 and the object in here is the person type. 18 00:00:33,120 --> 00:00:34,860 And that's because this, if check is doing all 19 00:00:34,860 --> 00:00:39,060 of that fancy narrowing for me, now this works great as is 20 00:00:39,060 --> 00:00:42,210 but as soon as I make my code a little bit more complicated 21 00:00:42,210 --> 00:00:44,100 by changing this to an unknown type 22 00:00:44,100 --> 00:00:46,980 because I don't actually know what this thing is 23 00:00:46,980 --> 00:00:48,750 now I have problems because you know I first 24 00:00:48,750 --> 00:00:51,000 need to check to see if this object is even an object. 25 00:00:51,000 --> 00:00:51,870 So I can say type 26 00:00:51,870 --> 00:00:56,870 of object is equal to object and it has a name property. 27 00:00:58,350 --> 00:01:00,150 You can see printing out this name works fine 28 00:01:00,150 --> 00:01:02,130 but it doesn't know what this object type is. 29 00:01:02,130 --> 00:01:04,560 It doesn't know that it is a person in our case. 30 00:01:04,560 --> 00:01:05,940 And same thing down here, it doesn't know 31 00:01:05,940 --> 00:01:09,180 that this is a to do, which is not obviously that great. 32 00:01:09,180 --> 00:01:10,830 Another problem with this, if we go back 33 00:01:10,830 --> 00:01:12,990 to what we had with our code before is imagine 34 00:01:12,990 --> 00:01:15,480 that this if check is much more complicated 35 00:01:15,480 --> 00:01:17,790 it maybe has like six or seven things it needs to check 36 00:01:17,790 --> 00:01:19,830 for to determine if this is a person 37 00:01:19,830 --> 00:01:22,442 versus a to-do, well to do all of that inside 38 00:01:22,442 --> 00:01:25,110 of this if is not ideal 'cause it takes up a lot 39 00:01:25,110 --> 00:01:27,360 of space and if I need to make that check in other places 40 00:01:27,360 --> 00:01:29,850 I now have to copy and paste that code a bunch of places. 41 00:01:29,850 --> 00:01:30,683 So you may think 42 00:01:30,683 --> 00:01:33,270 well I'm just gonna create a simple is person function 43 00:01:33,270 --> 00:01:35,100 and this is gonna take an object that could 44 00:01:35,100 --> 00:01:39,383 either be a person or a to-do and it's gonna return 45 00:01:39,383 --> 00:01:41,550 to me whether or not this is a person or not. 46 00:01:41,550 --> 00:01:43,920 So I'm gonna take this exact same if check up here 47 00:01:43,920 --> 00:01:45,870 I'm just gonna return that if this is true 48 00:01:45,870 --> 00:01:48,360 it's a person if it's false, I'm using a to-do so 49 00:01:48,360 --> 00:01:50,970 here I'll just call is person and I'll pass 50 00:01:50,970 --> 00:01:53,970 in my object but I'm getting some TypeScript errors. 51 00:01:53,970 --> 00:01:54,803 And the reason 52 00:01:54,803 --> 00:01:57,300 for that is typescripts not smart enough to look 53 00:01:57,300 --> 00:02:00,150 at each of your functions and figure out what it's doing. 54 00:02:00,150 --> 00:02:01,590 So it doesn't look at this function. 55 00:02:01,590 --> 00:02:03,750 All it looks at is your code right here. 56 00:02:03,750 --> 00:02:05,606 And the code right here is essentially just saying okay 57 00:02:05,606 --> 00:02:07,170 I do some random if check 58 00:02:07,170 --> 00:02:08,280 I don't know what happens in there 59 00:02:08,280 --> 00:02:09,750 and then I'm trying to access my object. 60 00:02:09,750 --> 00:02:12,060 Well it could still be a person or to-do, 61 00:02:12,060 --> 00:02:14,047 type script's not smart enough to know which one it is 62 00:02:14,047 --> 00:02:15,510 at this point. 63 00:02:15,510 --> 00:02:17,580 'Cause again, it can't look inside this function. 64 00:02:17,580 --> 00:02:19,080 So when you extract these like 65 00:02:19,080 --> 00:02:22,410 if checks to determine what type things are into a function 66 00:02:22,410 --> 00:02:23,434 TypeScript doesn't know 67 00:02:23,434 --> 00:02:25,830 that you're doing a type narrowing here 68 00:02:25,830 --> 00:02:27,870 and you need to help it out by telling it. 69 00:02:27,870 --> 00:02:29,400 You are doing type narrowing. 70 00:02:29,400 --> 00:02:31,800 And that's the whole idea behind a type predicate. 71 00:02:31,800 --> 00:02:34,740 Essentially when you have a function that returns a Boolean 72 00:02:34,740 --> 00:02:37,530 but it also narrows the type of something 73 00:02:37,530 --> 00:02:39,270 then that's called a type predicate. 74 00:02:39,270 --> 00:02:41,310 In our case we're returning a Boolean true false 75 00:02:41,310 --> 00:02:43,320 if this thing is a person or not. 76 00:02:43,320 --> 00:02:44,730 And I'm going to be narrowing it 77 00:02:44,730 --> 00:02:47,750 down by saying that it is specifically a person object. 78 00:02:47,750 --> 00:02:50,190 So to take this function and convert it to a type predicate 79 00:02:50,190 --> 00:02:52,200 all we need to do is define a return type. 80 00:02:52,200 --> 00:02:54,630 And this return type specifically is going to 81 00:02:54,630 --> 00:02:56,820 name whatever variable we're trying to narrow 82 00:02:56,820 --> 00:02:59,250 which in our case is this OBJ variable. 83 00:02:59,250 --> 00:03:00,240 That's variable right here. 84 00:03:00,240 --> 00:03:02,280 And then we need to say is by saying 85 00:03:02,280 --> 00:03:04,290 that we are saying it is a specific type 86 00:03:04,290 --> 00:03:05,940 and we're saying it is a person. 87 00:03:05,940 --> 00:03:08,190 So if this value here returns true 88 00:03:08,190 --> 00:03:09,990 so if this function is true, then it means 89 00:03:09,990 --> 00:03:12,810 that this OBJ variable is going to be a person. 90 00:03:12,810 --> 00:03:16,770 So whatever I pass into this is person, if it returns true 91 00:03:16,770 --> 00:03:19,880 then it knows for a fact that that variable is a person. 92 00:03:19,880 --> 00:03:22,200 So it allows TypeScript to do its type narrowing 93 00:03:22,200 --> 00:03:26,100 by you explicitly telling it what the type of that thing is. 94 00:03:26,100 --> 00:03:27,840 Now there are a few downsides to this 95 00:03:27,840 --> 00:03:29,700 though and that is that it's kind of similar 96 00:03:29,700 --> 00:03:32,640 to using the as property and just manually casting something 97 00:03:32,640 --> 00:03:35,820 because this does not have to be a correct assertion. 98 00:03:35,820 --> 00:03:37,110 I could come in here and say, you know what 99 00:03:37,110 --> 00:03:39,600 it's actually going to be a to-do if this is true. 100 00:03:39,600 --> 00:03:42,300 Now as you can see, this is actually lying 101 00:03:42,300 --> 00:03:45,120 to TypeScript because if this is true, it's a person 102 00:03:45,120 --> 00:03:47,430 but I'm telling it it's actually a to-do instead. 103 00:03:47,430 --> 00:03:48,990 And as you can see, I'm getting errors in my code 104 00:03:48,990 --> 00:03:52,050 that I shouldn't be getting because I'm lying to TypeScript. 105 00:03:52,050 --> 00:03:53,490 So it's really important that if you end up 106 00:03:53,490 --> 00:03:55,830 using these types of function predicates 107 00:03:55,830 --> 00:03:57,840 or these type predicates in TypeScript 108 00:03:57,840 --> 00:03:59,550 that you're very careful to make sure 109 00:03:59,550 --> 00:04:01,680 that whatever you cast this thing as 110 00:04:01,680 --> 00:04:04,320 in the is section is a hundred percent correct 111 00:04:04,320 --> 00:04:05,400 and will never be wrong 112 00:04:05,400 --> 00:04:07,710 'cause otherwise you're going to be lying to TypeScript. 113 00:04:07,710 --> 00:04:09,990 So really how these type predicates work is you 114 00:04:09,990 --> 00:04:12,330 define a normal function that takes in some type 115 00:04:12,330 --> 00:04:15,390 of objects or variable that you want to narrow the type of 116 00:04:15,390 --> 00:04:16,530 then you return true 117 00:04:16,530 --> 00:04:18,300 if the type should be narrowed or false 118 00:04:18,300 --> 00:04:20,940 if it should not be, if it returns true, then 119 00:04:20,940 --> 00:04:23,280 that means the type will be cast to whatever you set in 120 00:04:23,280 --> 00:04:25,860 this is section right here and if you return false 121 00:04:25,860 --> 00:04:27,540 nothing at all is going to happen. 122 00:04:27,540 --> 00:04:28,373 Now to look 123 00:04:28,373 --> 00:04:30,360 at a little bit more advanced real world example 124 00:04:30,360 --> 00:04:32,580 imagine here that we have some priority array 125 00:04:32,580 --> 00:04:33,900 and then we have a to-do which is 126 00:04:33,900 --> 00:04:35,880 either a title or a description. 127 00:04:35,880 --> 00:04:37,380 And then let's say I wanna do something 128 00:04:37,380 --> 00:04:39,930 with that to-do whatever, it doesn't really matter. 129 00:04:41,010 --> 00:04:42,660 There we go. 130 00:04:42,660 --> 00:04:44,340 So inside of this function I'm trying to do something 131 00:04:44,340 --> 00:04:46,590 with the to-do and in this case I wanted to determine 132 00:04:46,590 --> 00:04:49,680 if my description is describing a priority 133 00:04:49,680 --> 00:04:52,530 because maybe inside of my to-do, you know I give it a title 134 00:04:52,530 --> 00:04:55,890 of like do laundry and then in the description I just write 135 00:04:55,890 --> 00:04:57,240 you know this is a high priority to-do. 136 00:04:57,240 --> 00:04:58,260 So I write high. 137 00:04:58,260 --> 00:05:00,030 So if I want to check to determine 138 00:05:00,030 --> 00:05:02,280 if my description is just a generic description, such 139 00:05:02,280 --> 00:05:06,510 as like add whites, then darks or if I wanna determine 140 00:05:06,510 --> 00:05:09,630 if it's actually a specific thing, like high, medium, low 141 00:05:09,630 --> 00:05:11,820 well this is a great use case for these type predicates 142 00:05:11,820 --> 00:05:13,740 'cause this description is either going to be a string 143 00:05:13,740 --> 00:05:16,280 or it could be this more specific priority. 144 00:05:16,280 --> 00:05:18,758 So what I can do inside of here is I can just create a quick 145 00:05:18,758 --> 00:05:23,758 if check and I could say if is priority, I'll pass 146 00:05:23,910 --> 00:05:27,540 in my to-do description just like that. 147 00:05:27,540 --> 00:05:29,820 And then let's come down here and define that function 148 00:05:29,820 --> 00:05:33,690 is priority, which is going to take in a description 149 00:05:33,690 --> 00:05:35,370 which is a string. 150 00:05:35,370 --> 00:05:38,350 And here I just wanna return if my status is 151 00:05:40,735 --> 00:05:42,150 what I call this priority not status. 152 00:05:42,150 --> 00:05:43,622 There we go. 153 00:05:43,622 --> 00:05:45,690 Priority dot includes my description, there we go. 154 00:05:45,690 --> 00:05:48,900 So if my description is one of these three different things 155 00:05:48,900 --> 00:05:50,640 then I know that it is a description. 156 00:05:50,640 --> 00:05:51,870 So I wanna say that 157 00:05:51,870 --> 00:05:56,870 in that case my description here is going to be 158 00:05:58,140 --> 00:06:01,230 a priority, but I don't have a priority type yet. 159 00:06:01,230 --> 00:06:02,280 So let's just create one. 160 00:06:02,280 --> 00:06:04,860 The priority type is just going to 161 00:06:04,860 --> 00:06:08,840 be using the priority variable priorities. 162 00:06:11,439 --> 00:06:12,543 There we go. 163 00:06:13,410 --> 00:06:16,140 I should probably use this plural in all these cases. 164 00:06:16,140 --> 00:06:16,980 There we go. 165 00:06:16,980 --> 00:06:20,613 So it'd be priorities number, I wanna get the type of that. 166 00:06:24,210 --> 00:06:25,043 There we go. 167 00:06:25,043 --> 00:06:26,940 So this priority type right here is just going to be one 168 00:06:26,940 --> 00:06:28,380 of those three different types 169 00:06:28,380 --> 00:06:30,510 and I just need to make sure I add as constant 170 00:06:30,510 --> 00:06:32,280 and now you can see it's either high, medium or low. 171 00:06:32,280 --> 00:06:33,113 So I wanna say 172 00:06:33,113 --> 00:06:35,940 that this is going to be one of those different priorities. 173 00:06:35,940 --> 00:06:37,800 Now immediately you'll notice we get a bit of an error. 174 00:06:37,800 --> 00:06:38,633 It's saying 175 00:06:38,633 --> 00:06:40,920 that description here is not high, medium, or low. 176 00:06:40,920 --> 00:06:43,680 So it doesn't know, it can't call includes essentially. 177 00:06:43,680 --> 00:06:45,480 So a lot of times when you're doing these type 178 00:06:45,480 --> 00:06:48,060 predicates you may need to do some casting inside of here. 179 00:06:48,060 --> 00:06:50,430 So I can say I wanna cast this to a priority. 180 00:06:50,430 --> 00:06:52,980 Essentially what I'm saying here is that inside 181 00:06:52,980 --> 00:06:54,341 of this function I'm going to assume 182 00:06:54,341 --> 00:06:57,450 that description is a priority and then do all my checks. 183 00:06:57,450 --> 00:06:59,910 And this is pretty easy and safe to do just 184 00:06:59,910 --> 00:07:02,580 because I'm not like casting this to be used other places. 185 00:07:02,580 --> 00:07:04,050 It's only cast inside 186 00:07:04,050 --> 00:07:05,940 of this specific function to make it work. 187 00:07:05,940 --> 00:07:08,070 And then I'm just going to let my code assume 188 00:07:08,070 --> 00:07:11,002 that this is a priority run and if it returns false 189 00:07:11,002 --> 00:07:13,890 then my predicate here that's saying, is this a priority? 190 00:07:13,890 --> 00:07:15,090 We'll return false and it'll know 191 00:07:15,090 --> 00:07:17,370 that this is not actually a priority. 192 00:07:17,370 --> 00:07:19,260 So now in this case, if is priority 193 00:07:19,260 --> 00:07:21,000 we can check our todo.description 194 00:07:21,000 --> 00:07:24,780 and you'll see now it is either high, medium or low outside 195 00:07:24,780 --> 00:07:27,750 of this if statement inside this else section 196 00:07:27,750 --> 00:07:30,120 our todo.description is just a generic string 197 00:07:30,120 --> 00:07:32,030 because it's not actually a priority. 198 00:07:32,030 --> 00:07:33,690 So this is one kind of example 199 00:07:33,690 --> 00:07:34,890 of where you may want to use this. 200 00:07:34,890 --> 00:07:37,080 Again, it's not something you use super often 201 00:07:37,080 --> 00:07:39,750 and I would almost always recommend using other types 202 00:07:39,750 --> 00:07:41,280 of type narrowing or something 203 00:07:41,280 --> 00:07:43,890 like discriminated unions to solve this problem 204 00:07:43,890 --> 00:07:45,655 if you can and only fall back 205 00:07:45,655 --> 00:07:48,960 on these type predicates if you absolutely need them. 206 00:07:48,960 --> 00:07:50,700 Again, the reason for that is because a lot 207 00:07:50,700 --> 00:07:52,440 of times you need to use as within them 208 00:07:52,440 --> 00:07:53,700 which I don't really like 209 00:07:53,700 --> 00:07:57,150 and this is keyword to redefine the type of, this is a bit 210 00:07:57,150 --> 00:07:59,190 of a as kind of thing because I could just say 211 00:07:59,190 --> 00:08:01,590 you know what, actually this is a number and now you can see 212 00:08:01,590 --> 00:08:03,720 that it thinks that this thing is going to be a number 213 00:08:03,720 --> 00:08:04,950 or something like that. 214 00:08:04,950 --> 00:08:06,870 And in this case, the reason it's not letting me assign this 215 00:08:06,870 --> 00:08:08,460 to a number is 'cause strings can't be numbers 216 00:08:08,460 --> 00:08:10,890 but I could say that it's going to be the string SFD 217 00:08:10,890 --> 00:08:12,270 and now you can see it's SFD 218 00:08:12,270 --> 00:08:14,733 when in reality it's really high, medium, or low.