Designing Your Types 11 exercises

Constraining Type Parameters

We want to set a constraint on TError to ensure that it is an object with a message string property. We also want to set Error as the default type for TError.

To do this, we'll use the extends keyword and specify the object:

type Result<TResult, TError extends { message: string }

Loading solution


00:00 Okay, ready? In our type result, we have tResult, and then we have tError. And what we wanna say is that this tError needs to be constrained to be an object with message string on it. For that, we can use the extends keyword. And we can say tError, and then we just pass in anything we want to here.

00:19 And so this constraint, we could constrain it to be number if we wanted to. But then all of this stuff would start erroring down the bottom here, and actually our default would also error because it doesn't satisfy this constraint, ha! So we need something that matches error and also handles all this message string stuff.

00:36 So we can say tError extends message string. And we save the file, bam! Look at this, all great. And if we see this error here, type string does not satisfy the constraint message string. So extends kind of acts like, if we were to think about this, let's say const result equals,

00:55 and then we say a, or rather, tResults, or maybe just results actually. Results can be anything, so we'll just have it in as any. And then we'll say error. And then we're basically adding a constraint to this saying message string like this. Isn't that interesting?

01:14 So this is basically the analog here. By default, you don't have to provide a constraint. This is unlike in actual JavaScript where you do have to provide a constraint. And tError, because we know that it's constrained to be message string, we can basically add this annotation here, which is exactly the same here.

01:33 And we can also say error is message string equals error. Look at that. We've basically matched the same code here. I think we might need to say a new error to make this work or something. You get the idea. But this is basically analogous to this. So fantastic.

01:50 We now know how to constrain our type parameters and we know how to add defaults to them. So we basically have the same skillset as we do with runtime parameters too. So you can add as many constraints as you want to. You can add a constraint to tResult if you want to saying it must always extend a string

02:07 or must always extend something with an ID string property. And we noticed too that this constraint, like sure, it's constrained to be the shape, but we can add other properties to it. So we can add this code number. And if we hover over this, you can see the code number is still preserved in the outputs.

02:24 So it operates slightly differently to a runtime argument where on a runtime argument, that type can only ever be that. But here you basically pass in a base type that it must satisfy, must satisfy that constraint. And if it does satisfy that constraint, then bam, you're good to go and you can pass other properties in there too.

02:43 So there we get type parameter constraints.