## Notes
The ability to create a function or component to work with different data types/shapes.
```typescript
function identity<Type>(arg: Type): Type {
// the function maintains knowledge of the type of the input
return arg;
}
function badIdentity(arg: any): any {
// does not retain any information about the input
return arg;
}
```
There are two ways to call `identity` from above:
```typescript
const narrowIdentity = identity<string>("myString"); // this is preferred if using some sort of inheritance
const inferredIdentity = identity("myString");
```
Can also reference the above function like so
```typescript
let myIdentity: <Type>(arg: Type) => Type = identity;
let inputIdentity: <Input>(arg: Input) => Input = identity;
// as a call signature of an object literal type
let objectLiteralIdentity: { <Type>(arg: Type): Type } = identity;
```
## Generic Interface
```typescript
interface GenericIdentityFn {
<Type>(arg: Type): Type;
}
function identity<Type>(arg: Type): Type {
return arg;
}
let myIdentity: GenericIdentityFn = identity;
```
We can also move the generic *parameter* to be a parameter of the whole interface.
```typescript
interface GenericIdentityFn<Type> {
(arg: Type): Type;
}
function identity<Type>(arg: Type): Type {
return arg;
}
let myNumberIdentity: GenericIdentityFn<number> = identity;
```
## Generics in Classes
```typescript
// example
class GenericNumber<NumType> {
zeroValue: NumType;
add: (x: NumType, y: NumType) => NumType;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zerValue = 0;
myGenericNumber.add = function (x, y) {
return x + y;
}
```
## Generic Constraints
Let's say that we want to only use types that have the `.length` property. The following would not work:
```typescript
function loggingIdentity<Type>(arg: Type): Type {
console.log(arg.length);
// the above gets a dev-time error saying
// Property 'length' does not exist on type 'Type'
return arg;
}
```
Instead we could do the following
```typescript
interface Lengthwise {
length: number;
}
function loggingIdentity<Type extends Lengthwise>(arg: Type): Type {
console.log(arg.length); // no more error
return arg;
}
// compile-time error for inputs that don't have `.length`
loggingIdentity(3); // Argument of type 'number' is not assignable to parameter of type 'Lengthwise'
```
In short, a generic constraint is enforced in the generic parameter, aka `<Type extends ...>` within:
`function myFunction<Type extends Interface>(arg: Type): Type`
## Type Parameters in Generic Constraints
To ensure that we're not accessing a property that doesn't exist.
```typescript
function getProperty<Type, Key extends typeof Type>(obj: Type, key: Key) {
return obj[key];
}
const x = { a: 1, b: 2, c: 3, d: 4 };
getProperty(x, "a"); // fine
getProperty(x, "n"); // Argument of type 'm' is not assignable to parameter of type 'a' | 'b' | 'c' | 'd'
```
## Using Class Types in Generics
When creating factories in [[TypeScript]] using generics, it is necessary to refer to class types by the constructor functions
```typescript
function create<Type>(c: { new (): Type }): Type {
return new c();
}
```
A more advanced example uses the prototype property to infer and constrain relationships between the constructor function and the instance side of class types.
```typescript
class BeeKeeper {
hasMask: boolean = true;
}
class ZooKeeper {
nametag: string = 'Dale';
}
class Animal {
numLegs: number = 4
}
class Bee extends Animal {
numLegs = 6;
keeper: BeeKeeper = new BeeKeeper();
}
class Lion extends Animal {
keeper: ZooKeeper = new ZooKeeper();
}
function createInstance<A extends Animal>(c: new () => A): A {
return new c();
}
createInstance(Lion).keeper.nametag;
createInstance(Bee).keeper.hasMask;
```
The above is used to power [[TypeScript Mixins]].
## References
- [Generics - TypeScript Lang](https://www.typescriptlang.org/docs/handbook/2/generics.html)