TypeScript Fundamentals, v3 Interfaces
Transcript from the "Interfaces" Lesson
>> What about interfaces? So interfaces are more limited than type aliases, in that they can only be used to define what we call object types. And object types are things that are conceivably look kinda like a class instance are an object with properties. Important to realize that union type operators anything like this, make something not an object type.
[00:00:31] That's a little bit more complicated than an object type, right? Because it's like, it has these two possibilities, it could be one or the other, and interface cannot describe that. It could describe what's being used within something that uses one of these operators for sure, but not this whole thing.
[00:00:52] You could not describe that with an interface, so it is more limited. Just like type aliases you can import and export interfaces from modules, just like type aliases, they completely compile away and disappear as part of your build process. Inheritance has a lot more formality around it with interfaces.
[00:01:14] So TypeScript calls, keywords like extends, and we're gonna talk about another one in a moment. Implements, calls these things heritage clauses, and that they are used to describe ancestry in an object oriented hierarchy of some sort. Extends is used to describe inheritance between like things. Implements is used to describe inheritance between unlike things.
[00:02:14] And potentially the methods, like this is the way you would type a method, right? I'm gonna eat some food. We'll talk about void a little bit later just for now it's doesn't return anything. So we'll eat some food. When you say Dog implements AnimalLike, what you're saying is, Make sure that my Dog class does everything that an animal like interface requires.
[00:02:44] Every instance of dog should make AnimalLike as an interface happy, right? And we're missing this eat method. So this is a good way, interfaces are a great way to define contracts between things. And to say maybe I have these five or six different classes, maybe they all have some support for logging or for, I don't know.
[00:03:13] Some common like cross-cutting thing that your code base needs, it could define an interface, you could use that interface for many classes. And you're assured that TypeScript will make sure each of these has what it needs. So we can see that the error we saw above goes away as soon as we add the method.
[00:04:00] Multiple inheritance makes things wildly complicated. This is a way to have multiple facades. To have a class that aligns with multiple contracts. I'm calling these contracts, right? It's a commitment to do everything the interface says you must do. You can implement many interfaces. So here we've got a class called LivingOrganism, we've got AnimalLike we've got CanBark.
[00:04:28] So these are both interfaces here. And we can see that the dog can have a base class. And it can implement as many interfaces as it wants down here. No problem, no problem at all. So it is possible. And I'll pop this out in the in the playground here so I can play with it a little bit.
[00:04:50] It is totally possible if you had a type alias that looked like this, You can extend or you can implement, [LAUGH] right? A type alias, we're seeing that right here. However, you're kinda asking for trouble here. Because as soon as this type alias becomes more complicated, the compiler is going to object.
[00:05:16] So I prefer to exclusively use interfaces with this implement keyword, because the rules that interfaces demand that you align with guarantee that they will work nicely with classes. Imagine a case where you're using this type in a bunch of different places. Sometimes with the class, sometimes to define the argument of a function, you may find it useful to add something like this but your object oriented inheritance is not gonna work there.
[00:06:18] So yeah, you could use a type alias here as long as it's not something like this, right? It would have to just be an object type. So I recommend, just use interfaces with implements. All right, interfaces are open. You may have heard about this. You may have read about this.
[00:06:36] What does it mean? It means that unlike with type aliases, we can have multiple declarations here, right? Where we've got interface AnimalLike, interface AnimalLike. We have a twice in a file. If this were a class or a variable or function, something like that, we would be screamed at saying, look, you already defined this thing, I already have something by this name.
[00:06:59] You can't do that again. You can't write on top of it or whatever, whatever you're trying to do, you can't do it. Well, with interfaces, you absolutely can. And what effectively is happening here is up here we're saying we have one method it's called isAlive and down here, we're augmenting the interface.
[00:07:20] We're basically saying, look, there exists this AnimalLike thing add on top of it, an eat method. And note that the fact that we're doing this down here, It's a pervasive change that's happening. It doesn't matter that we're doing it lower down in the code, the eat method is clearly available right here.
[00:07:42] In fact this would have ramifications throughout your project. If you had this in one file your eat method, as a result of this would pop up everywhere. This is not a localized thing. This is a holistic across your app change potentially. Anywhere that you're using this AnimalLike type, you would see this change.
[00:08:07] You would see this eat method. Why is this useful? Why would I care about this? Where and how is this useful? I've highlighted it for you because it's probably a burning question. Well, sometimes we pin things on the globals, right? Like we add stuff to the window object.
[00:08:47] And you can see that we're able to do that here. So window, this is like the window that's available in your web browser, right? Window.document, you're used to running this, right? So by just having this little piece of code here, we're augmenting window, we're changing it, such that, We've got all of this stuff on here, right?
[00:09:13] But we've also got our example property, so we're sort of tacking something on. You'll find little things like this. You may think of like, globals are bad, right? We don't wanna do this, but you'll find that you're consuming some library. The type information is incomplete, you just wanna tack this one thing on, this is a way to do it, this is a good way to do it.
[00:09:38] Augmenting an interface. Because they're open, you can always add to them. So which one to use and when? Most of the time to be honest, it's completely up to you. There are fewer and fewer advantages as time goes on to fewer and fewer places where one makes any more sense than the other.
[00:10:03] However, A couple clear, Choices to make in some scenarios. Number one, if you're using anything that does not align with this idea of an object type, right? Where if you're using this union type operator, you must use a type alias, you cannot use an interface. Number two, this is my recommendation not a hard constraint.
[00:10:32] But if you want to use something that it's designed for a class to consume, like class dog implements, can eat food, or something like that. I would advise strongly that you use interfaces for that because you will by making that decision never find yourself in a place where the type you're creating becomes incompatible with object-oriented inheritance.
[00:10:58] Number three, if you deliberately want consumers of your types to augment your types, you must use an interface. Cases like this will become more clear in the intermediate TypeScript course. But there are situations where, A user of your code might sort of inject stuff into your interfaces so they can describe the code that they've written, which may be interacts with a library that you've written.
[00:11:32] But interfaces because they are open and type aliases are not, interfaces are the tool for this job. Finally, let's talk about recursive types. So by recursive types, I just simply mean types that are self referential. So if we look at this, let's pretend we just want nested arrays of numbers.
[00:11:53] How would we describe the type for this? Well, you could do it as follows. We could say, nested numbers is either a number or an array of nested numbers. So recently this is a TypeScript 3.7 feature. Type aliases started to support this. You used to have to use a combination of interfaces and types in order to accomplish this, but now, this becomes a lot easier to write.
[00:12:22] You're just saying it's a number or an array of myself. And as a result, we can do something like this. And you can see that .push is validated as it should, because it wants numbers or arrays of numbers, or recursive arrays of numbers. Thomas asks the question, kinda going back to interfaces here.
[00:12:47] Does augmentation of interfaces persist across files? Yes, type checking should be a holistic operation that's performed on your entire app at once. So, there is no such thing as local augmentation of an interface. It's a whole set of constraints for all of your code as it all fits together, including your dependencies.
[00:13:13] And that's type checking is all of that at once.