Typescript generics
A major part of software engineering is building components that not only have well-defined and consistent APIs, but are also reusable. Components that can handle today’s and tomorrow’s data will give you the most flexible ability to build large software systems.
Use generic type variables
function identity<Type>(arg: Type): Type { return arg; }
If we want to call the length of arg
When we do this, the compiler will give us an error, we are using the .length member of arg, but we are not saying that arg has this member, these type variables represent any and all types, so someone using this function may Pass in a number representative, which does not have a .length member.
function loggingIdentity<Type>(arg: Type[]): Type[] { console.log(arg.length); return arg; } //or function loggingIdentity<Type>(arg: Array<Type>): Array<Type> { console.log(arg.length); // Array has a .length, so no more error return arg; }
keyof type operator
The keyof operator takes an object type and produces a string or numeric literal union of its keys. The following type p has the same type as type P = “x” | “y”
type Point = { x: number; y: number }; type P = keyof Point;
If the type has a string or number index signature, keyof will return these types:
Reference to index type
type Arrayish={[n:number]:unknown} type A = keyof Arrayish; //type A = number type Mapish = { [k: string]: boolean }; type M = keyof Mapish; //type M = string | number
typeof type operator return type
Return type
- Use ReturnType to receive the return type of the basic type
type Predicate = (x: unknown) => boolean; type K = ReturnType<Predicate>; // type K = boolean
- If you use it on an object, you need to use typeof
Change to next
It should be noted here that Typescript intentionally limits the types of expressions you can use typeof.
Specifically, it is only legal to use typeof on an identifier (i.e., a variable name) or its properties. This helps avoid the confusing trap of writing code that you think is executing but isn’t
Index access type
The index access type here is different from the index type. The index type is such as [prop:number]:string, etc. The index access type simply accesses the type through the index.
type Person = { age: number; name: string; alive: boolean }; type Age = Person["age"]; //type Age = number
An index type is basically a type, so we can completely use unions, keyof, or other types
type Person = { age: number; name: string; alive: boolean }; type I1 = Person["age" | "name"]; //type I1 = string | number type I2 = Person[keyof Person]; //type I2 = string | number | boolean type AliveOrName = "alive" | "name"; type I3 = Person[AliveOrName]; //type I3 = string | boolean //If used directly without attributes, an error will be reported. type I1 = Person["alve"]; Property 'alve' does not exist on type 'Person'.
Another example of using arbitrary types for indexing is using number to get the type of an array element. We can combine this with typeof. Conveniently obtain the element type of an array literal
I have never known how to get the data type of a row of objects in an array, and now I finally have the answer.
Use type when indexing. Const cannot be used otherwise it will not take effect.
Refactoring with types
type key = "age"; type Age = Person[key];
Condition type
Have a good case record and keep it
Condition type constraints
Often a check of a conditional type will give us some new information, just as narrowing with type guards can give us a more specific type, a conditional type true branch will further restrict the generics by the type we check
type MessageOf<T> = T["message"]; Type '"message"' cannot be used to index type 'T'.
In this example, Typescript errors because T does not know that there is a property named message. We can constrain T and Typescript will no longer report errors.
type MessageOf<T extends { message: unknown }> = T["message"]; interface Email { message: string; } type EmailMessageContents = MessageOf<Email>; //type EmailMessageContents = string result display
Infer in conditional type
We just found ourselves using conditional types to apply constraints and then extract the types. This ended up being a common operation, and conditional types made it easier. Conditional types provide us with a way for the infer keyword to infer from the types we compare in our true branch. For example, we can infer the element type in Flatten instead of getting it manually using index access types:
type Flatten<Type> = Type extends Array<infer Item> ? Item : Type;
We use the infer keyword to declaratively introduce a new generic type variable called Item, instead of specifying how to retrieve the element type of Type in the true branch. This is a structure where we don’t have to think about how to dig and explore the types of things we’re interested in.
Mapping type
When you don’t want to repeat yourself, there are times when one genre needs to be based on another
Mapping types are built on the index signature syntax and are used to declare attribute types that have not been declared in advance.
interface Horse={}; type OnlyBoolsAndHorses = { [key: string]: boolean | Horse; }; const conforms: OnlyBoolsAndHorses = { del: true, rodney: false, };
A mapped type is a generic type that uses a union of PropertyKeys (often created via keyof) and iterates over the keys to create the type:
type OptionsFlags<Type> = { [Property in keyof Type]: boolean; };
The above code will change all properties of type Type and change their value to boolean
Mapping modifier
Two additional modifiers can be applied during mapping: readonly and ? affect mutability and optionality respectively.
These modifiers can be added by prefixing them with – or +. If you don’t add a prefix, + .
- Move out readonly
// Removes 'readonly' attributes from a type's properties type CreateMutable<Type> = { -readonly [Property in keyof Type]: Type[Property]; }; type LockedAccount = { readonly id: string; readonly name: string; }; type UnlockedAccount = CreateMutable<LockedAccount>; //result type UnlockedAccount = { id: string; name: string; }
- Remove is not required?
// Removes 'optional' attributes from a type's properties type Concrete<Type> = { [Property in keyof Type]-?: Type[Property]; }; type MaybeUser = { id: string; name?: string; age?: number; }; type User = Concrete<MaybeUser>; //result type User = { id: string; name: string; age: number; }
- Remap via as
type MappedTypeWithNewProperties<Type> = { [Properties in keyof Type as NewKeyType]: Type[Properties] }
Property names can be recreated using template literal types
type Getters<Type> = { [Property in keyof Type as `get${Capitalize<string & amp; Property>}`]: () => Type[Property] }; interface Person { name: string; age: number; location: string; } type LazyPerson = Getters<Person>; //result type LazyPerson = { getName: () => string; getAge: () => number; getLocation: () => string; }
Keys can be filtered out by generating never by conditional type
// Remove the 'kind' property type RemoveKindField<Type> = { [Property in keyof Type as Exclude<Property, "kind">]: Type[Property] }; interface Circle { kind: "circle"; radius: number; } type KindlessCircle = RemoveKindField<Circle>; //result type KindlessCircle = { radius: number; }
You can map any union, not just the string | number | symbol union, but any type of union:
type EventConfig<Events extends { kind: string }> = { [E in Events as E["kind"]]: (event: E) => void; } type SquareEvent = { kind: "square", x: number, y: number }; type CircleEvent = { kind: "circle", radius: number }; type Config = EventConfig<SquareEvent | CircleEvent> type Config = { square: (event: SquareEvent) => void; circle: (event: CircleEvent) => void; }
Template literal
The same syntax as a template literal string in JavaScript, but used in type position. When used with a specific literal type, a template literal generates a new string literal type by linking the content.
type World = "world"; type Greeting = `hello ${World}`; //Generate results type Greeting = "hello world"
A collection of every possible string literal that can be identified by each union member. It is recommended that large string unions be generated in advance and only used in smaller cases.
type EmailLocaleIDs = "welcome_email" | "email_heading"; type FooterLocaleIDs = "footer_title" | "footer_sendoff"; type AllLocaleIDs = `${EmailLocaleIDs | FooterLocaleIDs}_id`; //result type AllLocaleIDs = "welcome_email_id" | "email_heading_id" | "footer_title_id" | "footer_sendoff_id"