## 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)