This is the 28th day of my participation in the November Gwen Challenge. Check out the event details: The last Gwen Challenge 2021

The scope of generics

The type T defined below is not known until the function is called.

function createArray<T> (value: T, length: number) :T[] {
  let arr = [];
  for (let i = 0; i < length; i++) {
    arr[i] = value;
  }
  return arr;
}

let fooArray = createArray<string> ('foo'.3);
console.log(fooArray);
// ['foo', 'foo', 'foo']
Copy the code

So T can only be used inside the function, such as temporary variables arr defined above, it should also be each array for T, but due to the definition, not add type, so it is concluded that into each for any array, we can add a type, give it so it will have their own right type.

function createArray<T> (value: T, length: number) :T[] {
  // Where to modify
  let arr: T[] = [];
  for (let i = 0; i < length; i++) {
    arr[i] = value;
  }
  return arr;
}

let fooArray = createArray<string> ('foo'.3);
console.log(fooArray);
// ['foo', 'foo', 'foo']
Copy the code

Again, T can only be used inside a function. If used outside a function, an error will be reported.

Exercise: What is wrong about the scope of generics?

A. Generics have global scope

B. Generics have local scope

C. Generics have block-level scope in generic functions

The answer:

A

Resolution:

The scope of generics is local rather than global. For example, if we use generics in a generic function or a generic class, we can only use them internally, not externally. The (local) scope of generics in generic functions and generic classes is block-level scope. The inner closed loop is inaccessible outside the function or class.

Generic classes will be covered later.

Type inference in generics

CreateArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray createArray It will still deduce the string correctly.

function createArray<T> (value: T, length: number) :T[] {
  // Where to modify
  let arr: T[] = [];
  for (let i = 0; i < length; i++) {
    arr[i] = value;
  }
  return arr;
}

let fooArray = createArray('foo'.3);
console.log(fooArray);
// ['foo', 'foo', 'foo']
Copy the code

Problem: Based on code blocks, find variablesanimalThe type of

const animalInfo = <T>(arg: T) : {
  age: T; name: string; } = > {return {
    age: arg,
    name: 'panda'
  };
};

const animal = animalInfo(10);

// A
{
  age: string;
  name: string;
}
// B
{
  age: number;
  name: string;
}
// C
{
  age: T;
  name: string;
}

// D
{
  age: any;
  name: string;
}
Copy the code

The answer:

B

Resolution:

Function animalInfo returns a value of type

{
    age: T;
    name: string;
}
Copy the code

The type of the generic T is the parameter type of the function animalInfo.

const animal = animalInfo(10);
Copy the code

So the variable animal is of type number.

Multiple type parameters

For example, we define a function swap that takes a tuple, swaps its 0th item with its first item, and returns the output [7, ‘foo’].

function swap(tuple) {
  return [tuple[1], tuple[0]];
}

let swapped = swap(['foo'.7]);
// [7, 'foo']
Copy the code

Since we didn’t define any type, the swap here is naturally inferred to be an array with any input and any output, and its type is unknown when it uses its 0th entry.

At this point, if you want to add generics, you need multiple generic parameters.

function swap<T.U> (tuple: [T, U]) :U.T] {
  return [tuple[1], tuple[0]];
}

let swapped = swap<string.number> (['foo'.7]);
Copy the code

That way, it has the right type, and its return value can be correctly inferred from the type.

And of course we can omit the types that are defined here, there are type inferences that automatically infer the output of the function from the input of the function.

Exercise: Fill in the blanks with appropriate types

const animalInfo = <T, R>(arg1: T, arg2: R) : {
  age: T; name: R;
} => {
  return {
    age: arg1,
    name: arg2
  };
};

animalInfo<______, ______>(10, 'panda');
Copy the code

The answer:

number

string

Resolution:

This topic is relatively simple. Multiple generic parameters show the specified parameter type. The animalInfo function takes two arguments of type number and string. A. number B. string C. number D. string

The default type

Let’s study the generic default type, or to create an array of functions as an example, this function is introduced into two parameters need to be, the first is the array of the value of each item, the second is the length of the array, if we want the function in the call, a parameter can be zero, you can then these two parameters can be set as optional, then transform it.

function createArray<T> (value? : T, length? :number) :T[] {
  if (arguments.length === 0) {
    return [];
  }
  let arr: T[] = [];
  for (let i = 0; i < length; i++) {
    arr[i] = value;
  }
  return arr;
}

let emptyArray = createArray();
Copy the code

Call a function, we can call it without passing an argument.

But at this point, we find that the generics are inferred as follows.

We can of course use string to give it a generic type.

let emptyArray = createArray<string> ();Copy the code

Another way is to take advantage of the generic default type.

// Modify the following line
function createArray<T = string> (value? : T, length? :number) :T[] {
  if (arguments.length === 0) {
    return [];
  }
  let arr: T[] = [];
  for (let i = 0; i < length; i++) {
    arr[i] = value;
  }
  return arr;
}

let emptyArray = createArray();
Copy the code

In this case, even if string is not specified here, it is inferred that each item is an array of strings.

Exercise: Fill in generics based on code blocksTThe type of

const animalInfo = <T = number, R = string>(arg1? : T, arg2? : R) : { age: T | undefined; name: R | undefined; } => { return { age: arg1, name: arg2 }; }; // The type of the generic T is? animalInfo(); // The type of the generic T is? animalInfo('10', 'panda');Copy the code

The answer:

number

string

Resolution:

The default values T for number and R for string have been specified in the function animalInfo.

No arguments are passed in the first call, so the generic T is the default type number.

animalInfo()
Copy the code

In the second call, the first argument specifies type string, so the generic T is of type string.

animalInfo('10'.'panda');
Copy the code

Data: Generic constraints

When we use generics in functions, we do not know the type of the generic in advance, so we cannot access the properties or methods of that type at will.

function loggingIdentity<T> (arg: T) :T {
  console.log(arg.length); // Error: T doesn't have .length
  return arg;
}
Copy the code

In the example above, an error is reported because T does not necessarily contain the length attribute (for example, the number type does not have the length attribute).

We can use interfaces to constrain generics.

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise> (arg: T) :T {
  console.log(arg.length);
  return arg;
}
Copy the code

We use extends to constrain that the generic T must conform to the Lengthwise shape of the interface, that is, it must contain the Length attribute. If the arG passed in does not contain length when calling loggingIdentity, an error will be reported at compile time:

interface Lengthwise {
  length: number;
}

function loggingIdentity<T extends Lengthwise> (arg: T) :T {
  console.log(arg.length);
  return arg;
}

loggingIdentity(7);

// index.ts(10,17): error TS2345: Argument of type '7' is not assignable to parameter of type 'Lengthwise'.
Copy the code

Multiple type parameters can also constrain each other:

function copyFields<T extends U.U> (target: T, source: U) :T {
  for (let id in source) {
    target[id] = (<T>source)[id];
  }
  return target;
}

let x = { a: 1.b: 2.c: 3.d: 4 };

copyFields(x, { b: 10.d: 20 });
Copy the code