“This is the 18th day of my participation in the First Challenge 2022. For details: First Challenge 2022.”
preface
Generics are the hardest part of TS to understand, and with generics out of the way, TS is not that hard.
This is the knowledge graph of this article:
Have you got it? If you don’t, let’s fix it.
Before learning this article, it is necessary to have the basis of TS. If you find it difficult to read, you can learn this article first, which is an easy-to-understand summary of the basic knowledge of TS
Why generics?
If you’ve seen the TS documentation, you’ve probably seen the following two paragraphs:
In software engineering, it is important not only to create consistent, well-defined apis, but also to consider reusability. The ability of components to support not only current data types but also future data types gives you a lot of flexibility when building large systems.
In languages like C# and Java, generics can be used to create reusable components that can support multiple types of data. This allows users to use components with their own data types.
It’s not human language. Are you sure beginners can understand it?
I think it’s important for beginners to understand why generics are needed and what problems do they solve? Instead of looking at this tricky definition.
Let’s take a look at an example of the problem generics solve.
Print print print print print print print print print print print print print print print
function print(arg:string) :string {
console.log(arg)
return arg
}
Copy the code
Now the requirements have changed and I also need to print the number type, what do I do?
Can be modified using union types:
function print(arg:string | number) :string | number {
console.log(arg)
return arg
}
Copy the code
Now that the requirements have changed, I also need to print an array of strings, an array of numbers, or any type, what do I do?
There is a silly way to write as many union types as you can support.
Or change the parameter type to any.
function print(arg:any) :any {
console.log(arg)
return arg
}
Copy the code
Not to mention that the any type is bad, after all, try not to write any in TS.
And that’s not what we want, we can just say that the incoming value is of type ANY and the output value is of type ANY, and the incoming and returned values are not uniform.
It’s even buggy
const res:string = print(123)
Copy the code
We define a string to receive the return value of print. The return value is a number. TS does not give us an error message.
This is where generics come in, which can easily solve the problem of consistent input and output.
Note: Generics were not designed to solve this one problem. Generics solve many other problems as well.
Generics are basic
Processing function parameters
We use generics to solve the problem above.
The syntax for generics is to write type parameters in <>, usually represented by T.
function print<T> (arg:T) :T {
console.log(arg)
return arg
}
Copy the code
In this way, we achieve the same type of input and output, and can input and output any type.
If the types are inconsistent, an error is reported:
A T in a generic type is like a placeholder, or a variable, that can be used to pass in the defined type as an argument, and it can be printed unchanged.
The way generics are written is a little odd to front-end engineers, such as <> T, but remember that when you see <>, you know it’s a generic.
We can specify types in two ways when we use them.
- Define the type to use
- TS type inference, automatically deduce the type
print<string> ('hello') // define T as string
print('hello') // TS type inference, automatic inference type is string
Copy the code
We know that both type and interface can define function types. We can also use generics to write type like this:
type Print = <T>(arg: T) = > T
const printFn:Print = function print(arg) {
console.log(arg)
return arg
}
Copy the code
Interface:
function print<T> (arg:T) :T {
console.log(arg)
return arg
}
interface Iprint<T> {
(arg: T): T
}
const myPrinnt: Iprint<number> = print
Copy the code
The default parameters
To add a default parameter to a generic, write:
function print<T> (arg:T) :T {
console.log(arg)
return arg
}
interface Iprint<T = number> {
(arg: T): T
}
const myPrinnt: Iprint = print
Copy the code
So the default type is number, ok? Does T feel like a function argument?
Process multiple function parameters
Now there is a function that passes in a tuple with only two items, swaps the 0th and 1st items of the tuple, and returns the tuple.
function swap(tuple) {
return [tuple[1], tuple[0]]}Copy the code
So if we write it this way, we’re going to lose the type, and we’re going to rewrite it with generics.
We use T for the type of the 0th term and U for the type of the first term.
function swap<T.U> (tuple: [T, U]) :U.T]{
return [tuple[1], tuple[0]]}Copy the code
This allows control of the 0th and 1st items of the tuple.
In the argument passed in, the 0th item is of type string and the first item is of type number.
In the return value of the swap function, the 0th item is of type number and the first item is of type string.
The zeroth term is full of number methods.
The first item is full of string methods.
Function side effect operation
Generics can be used not only to easily constrain the parameter types of functions, but also when functions perform side effects.
For example, we have a generic asynchronous request method that wants to return different types of data for different URL requests.
function request(url:string) {
return fetch(url).then(res= > res.json())
}
Copy the code
Call an interface to get user information:
request('user/info').then(res= >{
console.log(res)
})
Copy the code
The result, res, is of type any, which is annoying.
We want the calling API to clearly know what data structure the return type is. We can do this:
interface UserInfo {
name: string
age: number
}
function request<T> (url:string) :Promise<T> {
return fetch(url).then(res= > res.json())
}
request<UserInfo>('user/info').then(res= >{
console.log(res)
})
Copy the code
In this way, you can easily get the data type returned by the interface, which greatly improves the development efficiency:
Constraints of generic
Suppose we now have a function that prints the length of the argument passed in. Let’s write it like this:
function printLength<T> (arg: T) :T {
console.log(arg.length)
return arg
}
Copy the code
T has a length attribute.
So now I want to constrain this generic to have a length property, what do I do?
You can combine it with interface to constrain the type.
interface ILength {
length: number
}
function printLength<T extends ILength> (arg: T) :T {
console.log(arg.length)
return arg
}
Copy the code
The key is that
makes this generic extend the interface ILength so that it can constrain generics.
Variables we define must have length attributes, such as STR, arr, and obj below, to compile with TS.
Const STR = printLength(' Lin ') const arr = printLength([1,2,3]) const obj = printLength({length: 10})Copy the code
This example again demonstrates duck typing of interfaces.
As long as you have the length property, it’s bound, it doesn’t matter if you’re STR, arr, obj.
Of course, if we define a variable that does not contain the length attribute, such as a number, we will get an error:
Some applications of generics
With generics, you can define a function, interface, or class without specifying a specific type up front. Instead, you specify the type when you use it.
Generics constrain classes
Define a stack with two methods: on and off the stack. If you want the elements on and off the stack to be of the same type, you can write:
class Stack<T> {
private data: T[] = []
push(item:T) {
return this.data.push(item)
}
pop():T | undefined {
return this.data.pop()
}
}
Copy the code
When defining an instance, write the type. For example, if both the stack and the stack are of type number, write:
const s1 = new Stack<number> ()Copy the code
This will cause an error when a string is pushed:
This is very flexible. If the requirements change, both the stack and the stack should be strings, which can be changed when defining instances:
const s1 = new Stack<string> ()Copy the code
This will cause an error when a number is pushed:
In particular, generics cannot constrain static members of a class.
It is an error to define the static keyword for the pop method
Generic constraint interfaces
Using generics, you can also modify an interface to make it more flexible.
interface IKeyValue<T, U> {
key: T
value: U
}
const k1:IKeyValue<number.string> = { key: 18.value: 'lin'}
const k2:IKeyValue<string.number> = { key: 'lin'.value: 18}
Copy the code
Generics define arrays
Define an array, as we wrote earlier:
const arr: number[] = [1.2.3]
Copy the code
Now I can also write this:
const arr: Array<number> = [1.2.3]
Copy the code
Array entry type error, error reported
In practice, generics constrain the backend interface parameter types
Let’s look at a very useful use of generics for project development, constraining back-end interface parameter types.
import axios from 'axios'
interface API {
'/book/detail': {
id: number,},'/book/comment': {
id: number
comment: string}... }function request<T extends keyof API> (url: T, obj: API[T]) {
return axios.post(url, obj)
}
request('/book/comment', {
id: 1.comment: 'Great! '
})
Copy the code
There will be reminders when the interface is called, such as:
Wrong path:
The parameter type was passed incorrectly:
Parameter transmission is short:
Development efficiency improves greatly! You can finish up with the back end early and get your hands dirty.
summary
Generics, literally, Generics are general and general.
Generics are defined when a function, interface, or class is defined without specifying a specific type in advance. Instead, the type is specified when it is used.
A T in a generic type is like a placeholder, or a variable, that can be used to pass in the defined type as an argument, and it can be printed unchanged.
Generics provide meaningful constraints between members: function parameters, function return values, instance members of a class, methods of a class, and so on.
Here’s a diagram summarizing the benefits of generics:
If my article is helpful to you, your like 👍 is my biggest support ^_^
To:
What is the difference between interface and type in TS?
Easy to understand the basic knowledge of TS summary