当前位置:网站首页>Typescript learning [9] generic

Typescript learning [9] generic

2022-06-09 04:46:00 Large American without sugar

What is generics ?

Java The interpretation of generics in language is : Generics are Type parameterization , That is to say, a specific type of A parameterized . Just like defining function parameters , We can define several type parameters for generics , And pass explicit type parameters to the generic type when calling . Generics are designed to Valid constraints on the relationship between type members , For example, function parameters and return values 、 The relationship between class or interface members and methods .

Generic Type Parameter

The most common scenario for generics is to Constrains the type of function arguments , We can define several parameters for a function that are explicitly typed only when called .

For example, one of the following definitions reflect function , It can accept a parameter of any type , And return the value and type of the parameter intact , So how do we describe this function ? It seems that it needs to be used unknown 了

function reflect(param: unknown) {
    
  return param;
}
const str = reflect('string'); // str  The type is  unknown
const num = reflect(1); // num  type  unknown

here ,reflect Although the function can accept an arbitrary type of parameter and return the value of the parameter intact , However, the return value type does not meet our expectations . Because we want the return value type to correspond to the input parameter type one by one ( such as number Yes number、string Yes string), Not regardless of the type of input parameter , The return value is always unknown.

here , Generics can meet this demand , How to define a generic parameter ? First , We put the parameters param The type of is defined as a ( Type level ) Parameters 、 Variable , Not an explicit type , Wait until the function call to pass in an explicit type .

For example, we can use angle brackets <> The syntax defines a generic parameter for a function P, And designate param The type of parameter is P , As shown in the following code :

function reflect<P>(param: P) {
    
  return param;
}

Here we can see , In angle brackets P Represents the definition of generic parameters ,param After P Indicates that the type of the parameter is generic P( That is, the type is affected by P constraint ).

We can also use generics to explicitly annotate the type of the return value , Although there is no need to ( Because the type of the return value can be inferred based on the context ). For example, call the following reflect when , We can use angle brackets <> Syntax to generic parameters P Explicitly pass in an explicit type .

function reflect<P>(param: P): P {
    
  return param;
}

Then when you call a function , We also pass <> The syntax specifies the following string、number Type entry , Accordingly ,reflectStr The type is string,reflectNum The type is number.

const str = reflect<string>('string'); // str  The type is  string
const num = reflect<number>(7); // num  The type is  number

Generics can not only constrain the type of the entire parameter of a function , You can also constrain parameter properties 、 Types of members , For example, the type of the parameter can be an array 、 object , The following example :

function reflectArray<P>(param: P[]) {
    
  return param;
}

const reflectArr = reflectArray([1, '1']); // reflectArr  The type is  (string | number)[]

Here we constrain param The type of is array , The element type of an array is a generic input parameter .

Be careful : Generic arguments to a function must be in conjunction with the arguments / Only when parameter members establish effective constraint relations can they have practical significance . For example, in the following example , We define a generic type that only constrains the return value type , It doesn't make any sense .

function uselessGeneries<P>(): P {
    
  return void 0 as unknown as P;
}

We can define any number of generic arguments to a function , As shown in the following code :

function reflectExtraParams<P, Q>(p1: P, p2: Q): [P, Q] {
    
  return [p1, [p2];
}

In the above code , We define a with two generic arguments (P and Q) Function of reflectExtraParams, And pass P and Q Constraint function parameters p1、p2 And the type of the return value .

Universal class

In the definition of the class , We can also use generics to constrain constructors 、 attribute 、 Type of method , As shown in the following code :

class Memory<S> {
    
  store: S;
  constructor(store: S) {
    
    this.store = store;
  }
  set(store: S) {
    
    this.store = store;
  }
  get() {
    
    return this.store;
  }
}

const numMemory = new  Memory(1);
const getNumMemory = numMemory.get(); //  The type is  number
numMemory.set(2);//  Write only  number  type 
console.log(numMemory.get()); // 2
const strMemory = new Memory('');
const getStrMemory = strMemory.get(); //  The type is  string
strMemory.set('string'); //  Write only  string  type 
console.log(strMemory.get()); // string 

First , We define a register class that supports reading and writing Memory, And use generic constraints Memory Class constructor function 、set and get Type of method parameter , Finally, the generic input parameters are instantiated number and string Two registers of type .

Generic classes are similar to generic functions in that , When creating a class instance , If a generic constrained parameter passes in an explicit value , Then the generic input parameter ( To be exact, the incoming type ) It can be defaulted

The generic type

stay TypeScript in , A type itself can be defined as a generic type with ambiguous type parameters , And can receive explicit types as input parameters , Thus deriving more specific types , As shown in the following code :

function reflect<P>(param: P):P {
    
  return param;
}

const reflectFn: <P>(param: P) => P = reflect; //  That's all right. 

Here we are variables reflectFn Explicitly added generic type annotations , And will reflect Function is assigned to it as a value .

We can also put reflectFn The type annotation of is extracted as a type alias or interface that can be reused , As shown in the following code :

function reflect<P>(param: P):P {
    
  return param;
}
type ReflectFunction = <P>(param: P) => P;
interface IReflcetFunction {
    
  <P>(param: P): P;
}

const reflectFun2: ReflectFunction = reflect;
const reflectFun3: IReflcetFunction = reflect;

Move the definition of the type input parameter after the type alias or interface name , At this point, the defined type that receives a specific type input parameter and returns a new type is a generic type .

In the following example , We have defined two parameters that can be received P Generic types of

function reflect<P>(param: P):P {
    
  return param;
}
type GenericReflectFunction<P> = (param: P) => P;
interface IGenericReflectFunction<P> {
    
  (param: P): P;
}
const reflectFun4: GenericReflectFunction<string> = reflect; //  Representational generics 
const reflectFun5: IGenericReflectFunction<number> = reflect; //  Representational generics 
const reflectFn4Return = reflectFun4('string'); //  Both input and return values must be  string  type 
const reflectFn5Return = reflectFun5(1); //  Both input and return values must be  number  type 

In a generic definition , We can even use some type operators to express , So that generics can derive different types according to the types of input parameters , As shown in the following code :

type StringOrNumberArray<E> = E extends string | number ? E[] : E;
type StringArray = StringOrNumberArray<string>; //  The type is  string[]
type NumberArray = StringOrNumberArray<number>; //  The type is  number[]
type NeverGot = StringOrNumberArray<boolean>; //  The type is  boolean

Here we define a generic type , If the reference is number | string An array type is generated , Otherwise, the input parameter type is generated . and , We've also used and JavaScript Ternary expressions use the same syntax to express the logical relationship of type operations .

If we pass in a for the above generic string | boolean Union type as input parameter , What type will you get ?

type StringOrNumberArray<E> = E extends string | number ? E[] : E;
type BooleanOrString = string | boolean;

type WhatIsThis = StringOrNumberArray<BooleanOrString>; //  It seems to be  string | boolean ?
type BooleanOrStringGot = BooleanOrString extends string | number ? BooleanOrString[] : BooleanOrString; // string | boolean

You will find that the type of display will be boolean | string[] .

BooleanOrStringGot and WhatIsThis The types of these two type aliases are different ? This is what we call Assignment condition type .

** About the concept of assignment condition type , Official interpretation :** In case of condition type judgment ( For example, in the above example extends), If the input parameter is a union type , Will be disassembled into independent ( atom ) type ( member ) Perform type operations .

For example, in the above example string | boolean Enter the reference , First it is disassembled into string and boolean These two independent types , Then judge whether it is string | number A subset of types . because string Is a subset and boolean No , So what we finally got WhatIsThis The type is boolean | string[].

Be careful : Enum types do not support generics .

Generic constraint

As mentioned earlier, generics are like functions of types , It can abstract 、 Encapsulate and receive ( type ) Enter the reference , The generic input parameter also has the characteristics similar to the function input parameter . therefore , We can limit generic input parameters to a relatively more explicit set , To constrain the input parameters .

For example, as mentioned earlier, return parameters intact reflect function , We want to limit the types of the received parameters to a set of several primitive types , You can use “ Generic input parameter name extends type ” Grammar achieves this , As shown in the following code :

function reflectSpecified<P extends number | string | boolean>(param: P): P {
    
  return param;
}

reflectSpecified('string');
reflectSpecified(7);
reflectSpecified(true);
reflectSpecified(null); // ts(2345) 'null'  Cannot assign type  'number | string | boolean'

Again , We can also constrain the interface generic input parameters to a specific scope , As shown in the following code :

interface ReduxModelSpecified<State extends {
      id: number; name: string }> {
    
  state: State;
}

type ComputedReduxModel1 = ReduxModelSpecified<{
     id: number; name: string }>;
type ComputedReduxModel2 = ReduxModelSpecified<{
     id: number; name: string; age: number }>;
type ComputedReduxModel3 = ReduxModelSpecified<{
     id: string; name: number }>; // ts(2344)
type ComputedReduxModel4 = ReduxModelSpecified<{
     id: number }>; // ts(2344)

In the example above ,ReduxModelSpecified Generics only receive { id: number; name: string } Subtypes of interface types are used as input parameters .

We can also set constraints between different generic input parameters , As shown in the following code :

interface ObjSetter {
    
  <O extends {
    }, K extends keyof O, V extends O[K]>(obj: O, key: K, value: V): V;
}

const setValueOfObj: ObjSetter = (obj, key, value) => (obj[key] = value);
setValueOfObj({
     id: 1, name: 'name' }, 'id', 2);
setValueOfObj({
     id: 1, name: 'name' }, 'name', 'newName');
setValueOfObj({
     id: 1, name: 'name' }, 'age', 2);
setValueOfObj({
     id: 1, name: 'name' }, 'id', '2');

When setting the function type of the object property value , It has a 3 Generic arguments : The first 1 One is the object , The first 2 One is the 1 A subset of the set of input parameter attribute names , The first 3 Is a subtype of the specified attribute type .

in addition , There is another similarity between generic input and function input , It can also specify default values for generic input parameters ( Default type ), And the syntax is completely consistent with the default parameters of the specified function , As shown in the following code :

interface ReduxModelSpecified<State extends {
      id: number; name: string }> {
    
  state: State
}
interface ReduxModelSpecified2<State = {
      id: number; name: string }> {
    
  state: State
}
type ComputedReduxModel5 = ReduxModelSpecified2;
type ComputedReduxModel6 = ReduxModelSpecified2<{
     id: number; name: string; }>;
type ComputedReduxModel7 = ReduxModelSpecified; // ts(2314)  Missing a type parameter 

In the example above , We have defined generic types with default types as input parameters ReduxModelSpecified2, Therefore use ReduxModelSpecified2 Time type input parameters can be defaulted . and ReduxModelSpecified There is no default value for the input parameter of , So the default input parameter will prompt a type error .

The constraints and default values of generic input parameters can also be combined , As shown in the following code :

interface ReduxModelMixed<State extends {
     } = {
      id: number; name: string }> {
    
  state: State
}

Here we qualify generics ReduxModelMixed Enter the reference State Must be {} Subtypes of types , It also specifies that the default type of the input parameter is the interface type by default { id: number; name: string; }.

原网站

版权声明
本文为[Large American without sugar]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/160/202206090433596655.html