To make generic functions that accept a value or an object as a parameter but with certain properties, you have to use the extends
keyword on the generic type parameter
and add some constraints on the generic type parameter for the function in TypeScript.
TL;DR
// an interface
interface ShouldHaveLengthProp {
length: number;
}
// a generic function that returns the
// value of the `length` property of
// the object passed to it
// with constraints for generic parameter type
function getLength<Type extends ShouldHaveLengthProp>(data: Type): number {
return data.length; // Valid ✅
}
// call the function
getLength(1); // Error ❌. Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
getLength(true); // Error ❌. Argument of type 'boolean' is not assignable to parameter of type '{ length: number; }'.
getLength([1, 2, 3, 4]); // Valid ✅. Returns => 4
getLength(["Roy", "Lily"]); // Valid ✅. Returns => 2
getLength({ name: "John", length: 5 }); // Valid ✅. Returns => 5
For example, let's say we need to make a generic function where it will return the value of the length
property of whatever type is passed to the function as an argument like this,
// a generic function that returns the
// value of the `length` property of
// the object passed to it
function getLength<Type>(data: Type): number {
return data.length; // Error ❌. Property 'length' does not exist on type 'Type'.
}
If you want to know more on creating Generic functions in TypeScript, See the blog on How to make Generic Functions that change return value type according to the parameter values type in TypeScript?.
As soon as you write the function code, the TypeScript compiler will start showing an error on accessing data.length
saying that Property 'length' does not exist on type 'Type'.
.
This is expected since the function can accept values of any kind of type
and not all type
in TypeScript has the length
property attached to them. The length
property is available to values of the array
, string
, and any object
type with a property called length
in it.
So what we need to do is add constraints to the generic type parameter
using the extends
keyword after the generic type parameter
name and then add the constraints we need to add there.
It can be done like this,
// a generic function that returns the
// value of the `length` property of
// the object passed to it
// with constraints for generic parameter type
function getLength<Type extends { length: number }>(data: Type): number {
return data.length; // Valid ✅
}
As you can see that we have added the constraint of {length: number}
after the extends
keyword. This will check the object that is passed to the getLength
function for the length
property which also has a type of number
. Adding constraints is like an if conditional checking for the generic type parameter
in TypeScript. Cool. Right!? 😃.
Now let's call the getLength
function and try to call the funtion with various value types like this,
// a generic function that returns the
// value of the `length` property of
// the object passed to it
// with constraints for generic parameter type
function getLength<Type extends { length: number }>(data: Type): number {
return data.length; // Valid ✅
}
// call the function
getLength(1); // Error ❌. Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
getLength(true); // Error ❌. Argument of type 'boolean' is not assignable to parameter of type '{ length: number; }'.
getLength([1, 2, 3, 4]); // Valid ✅. Returns => 4
getLength(["Roy", "Lily"]); // Valid ✅. Returns => 2
getLength({ name: "John", length: 5 }); // Valid ✅. Returns => 5
As you can see that the first 2 function calls with number
and boolean
values types have error since both these type doesn't have the property of length
in it and the last 3 functions with array
and an object
type are valid since both these values have the property of length
attached to it.
For better readability let's write the generic type parameter
constraint as a separate type called ShouldHaveLengthProp
like this,
// an interface
interface ShouldHaveLengthProp {
length: number;
}
// a generic function that returns the
// value of the `length` property of
// the object passed to it
// with constraints for generic parameter type
function getLength<Type extends ShouldHaveLengthProp>(data: Type): number {
return data.length; // Valid ✅
}
// call the function
getLength(1); // Error ❌. Argument of type 'number' is not assignable to parameter of type '{ length: number; }'.
getLength(true); // Error ❌. Argument of type 'boolean' is not assignable to parameter of type '{ length: number; }'.
getLength([1, 2, 3, 4]); // Valid ✅. Returns => 4
getLength(["Roy", "Lily"]); // Valid ✅. Returns => 2
getLength({ name: "John", length: 5 }); // Valid ✅. Returns => 5
We have successfully made a generic function that accepts an object with a certain property, in our case (the length
property). Yay 🥳!
See the above code live in codesandbox.
That's all 😃!