Generic Interface
Generic interfaces are very much like functions that transform types.
A generic interface lets us define a function signature, and enforce that signature on a future value that we define. Put another way, we are describing a generic function
interface GenericIdentityFn {
<Type>(arg: Type): Type;
}
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
In this case, when we use GenericIdentityFn
we now will also need to specify the corresponding type argument (here: number
), effectively locking in what the underlying call signature will use. This changes its use markedly from the previous implementation.
interface GenericIdentityFn<Type> {
(arg: Type): Type;
}
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: GenericIdentityFn<number> = identity;
This changes it slightly. Now we have a non-generic function signature that is a part of the generic type
- Understanding when to put the type parameter directly on the call signature and when to put it on the interface itself will be helpful in describing what aspects of a type are generic.
It’s not possible to refer to a generic interface without providing its type argument.
For example, if you want to write a function that accepts an instance of a generic interface, you have two options:
- Write a specialized function that accepts a very specific instance of the interface (e.g.
FormField<string>
) - Write a generic function
interface FormField<T> {
value?: T;
defaultValue: T;
isValid: boolean;
}
// Very specialized. Only works with `FormField<string>`.
function getStringFieldValue(field: FormField<string>): string {
if (!field.isValid || field.value === undefined) {
// Thanks to the specialization, the compiler knows the exact type of `field.defaultValue`.
return field.defaultValue.toLowerCase();
}
return field.value;
}
// Generic. Can be called with any `FormField`.
// It’s important to understand that here, T is the type argument of the function, not of the FormField interface. It gets passed only to FormField like any regular type does.
function getFieldValue<T>(field: FormField<T>): T {
if (!field.isValid || field.value === undefined) {
// On the other hand, we don't know anything about the type of `field.defaultValue`.
return field.defaultValue;
}
return field.value;
}
Generic constraints on interfaces (Type argument constraint)
you might decide to enforce the fact that FormField
should only contain string, number, or boolean values.
interface FormField<T extends string | number | boolean> {
value?: T;
defaultValue: T;
isValid: boolean;
}
// Type 'T' does not satisfy the constraint 'string | number | boolean'
function getFieldValue<T>(field: FormField<T>): T { /* ... */ }
The reason for this error is that the T
type argument of the function has no restrictions. You’re trying to pass it to FormField
which only accepts types that extend string, number or boolean. Therefore, you get a compile error. To get rid of the error, you need to put the same or stricter restrictions on the type argument T
.
interface FormField<T extends string | number | boolean> {
value?: T;
defaultValue: T;
isValid: boolean;
}
// Type 'T' does not satisfy the constraint 'string | number | boolean'
function getFieldValue<T extends string | number>(field: FormField<T>): T {
return field.value ?? field.defaultValue;
}
Children