TypeScript Type--Generic Type--Generic Constraints

1. Generic overview

Generics can allow functions to work with multiple types on the premise of ensuring type safety, so as to achieve reuse. It is often used in: functions, custom types, interfaces, etc.

Create an id function that returns the data itself (that is, the parameter and return value type are the same)

// 比如,该函数传入什么数值,就返回什么数值
function id(value: number): number {
    
     return value }

// res => 10
const res = id(10)
  • For example, id(10) calling the above function will directly return 10 itself. However, this function only accepts numeric types and cannot be used for other types
  • In order to allow the function to accept parameters of any type, the parameter type can be changed to any. However, in this way, the type protection of TS is lost, and the type is not safe
function id(value: any): any {
    
     return value }

At this time, you can use generics to achieve:

  • While ensuring type safety (without losing type information), generics allow functions, custom types, interfaces, etc. to work with a variety of different types, making them flexible and reusable
  • In fact, in programming languages ​​such as C# and Java, generics are one of the main tools used to realize the functions of reusable components

2. Generic functions

Create a generic function:

function id<Type>(value: Type): Type {
    
     return value }

// 也可以仅使用一个字母来作为类型变量的名称
function id<T>(value: T): T {
    
     return value }

grammar:

  • Add <> (angle brackets) after the function name, and add type variables in the angle brackets, such as Type here
  • Type variable Type, is a special type of variable that deals with types rather than values
  • The type variable is equivalent to a type container, which can capture the type provided by the user (the specific type is specified by the user when calling the function)
  • Because Type is a type, it can be used as the type of function parameters and return values, indicating that the parameters and return values ​​have the same type
  • Type variable Type, which can be any legal variable name

Call the generic function:

// 函数参数和返回值类型都为:number
const num = id<number>(10)

// 函数参数和返回值类型都为:string
const str = id<string>('a')

grammar:

  • Add <> (angle brackets) after the function name, and specify the specific type in the angle brackets, for example, number here
  • When the type number is passed in, this type will be captured by the type variable Type specified when the function is declared
  • At this time, the type of Type is number, so the type of the function id parameter and return value are also number
  • In this way, through generics, the id function can work with many different types, achieving reuse while ensuring type safety

3. Simplify generic function calls

When calling a generic function, you can 省略 <类型> simplify the call of the generic function

// 省略 <number> 调用函数
let num = id(10)
let str = id('a')
  • At this time, TS internally uses a mechanism called type parameter inference to automatically infer the type of the type variable Type according to the incoming actual parameter
  • For example, if the actual parameter 10 is passed in, TS will automatically infer the type number of the variable num and use it as the type of Type
  • Recommendation: Use this simplified way of calling generic functions to make the code shorter and easier to read
  • Note: When the compiler cannot infer the type or the inferred type is inaccurate, it is necessary to explicitly pass in the type parameter

4. Create a TS-based React project

Create a TS-based React project command:

npx create-react-app react-ts --template typescript

On the command line, add --template typescript to create a project that supports TS.
Compared to the JS React project, the directory changes:

  1. There is one more file in the project root directory: tsconfig.json (TS configuration file)
  2. In the src directory, the suffix of the file has changed from the original .js to .ts or .tsx
    ○ .ts The suffix of the ts file
    ○ .tsx is the suffix when using React components in TS. This suffix must be used whenever a JSX structure appears in the code
  3. In the src directory, there is an additional react-app-env.d.ts file
    ○ .d.ts type declaration file, used to specify the type
    ○ Note: Do not touch the src/react-app-env.d.ts file! ! !
// TS 中的 三斜线指令,作用类似于 import 用于指定对其他类型声明文件的依赖关系

// 此处,通过 types 来声明依赖于 react-scripts 包
// https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-
/// <reference types="react-scripts" />

Five, useState generic function

  • useState hook 是一个泛型函数, accepting a type variable specifying the type of state
// 指定 name 状态的类型为:string
const [name, setName] = useState<string>('jack')
// 指定 age 状态的类型为:number
const [age, setAge] = useState<number>(28)

Note: This type of variable not only specifies the type of the state, but also specifies the parameter type of the function that modifies the state such as setName

const [name, setName] = useState<string>('jack')
// 此时,setName 的参数的类型也是 string
setName('rose')
// 错误演示:
// setName(18)

Omitting the type variable simplifies the call to useState:

  • When using useState, as long as the initial value is provided, TS will automatically infer its type based on the initial value, so the type variable can be omitted
const [name, setName] = useState('jack')

Note: If the type automatically deduced by TS is inaccurate, you need to explicitly specify the generic type

6. useState clearly specifies the generic type

  • Interface to get channel list data and render
    ○ channel list data: http://geek.itheima.net/v1_0/channels
// 比如,频道列表数据是一个数组,所以,在 JS 中我们将其默认值设置为:[]
// 但是,在 TS 中使用时,如果仅仅将默认值设置为空数组,list 的类型被推断为:never[],此时,无法往数组中添加任何数据
const [list, setList] = useState([])
  • Note: The state of useState is complex data types such as arrays and objects, and the generic type needs to be clearly specified.
    ○ Although they are both arrays and objects, the array structures and object structures required by different requirements in project development are different. Therefore, its type needs to be explicitly specified
type Channel = {
    
    
  id: number
  name: string
}
// 明确指定状态的类型,此时,list 的类型为:Channel[]
// Channel[] 表示 Channel 类型的数组,也就是,数组中只能出现 Channel 类型的数据
const [list, setList] = useState<Channel[]>([])

7. Summary of generic types:

  • When using TS, you should write code with typed thinking. Simply put: first have a type, and then write logic code to use that type of data
  • For example, for objects and arrays, you should clearly specify the type of the object to be used, the type of the array, etc. before using them.
    Exercise:
// 假设,有以下学生信息,使用 useState 来存储该信息,并展示
{
    
     name, age, grade }

// 模拟获取学生信息
useEffect(() => {
    
    
  setTimeout(() => {
    
    
    setStudent({
    
    
      name: '张三',
      age: 13,
      grade: 3
    })
  }, 0)
}, [])
Redux + TS

8. Generic constraints

By default, the type variable Type of a generic function can represent any type, which results in no access to any properties

function id<Type>(value: Type): Type {
    
    
  // 报错: 类型“Type”上不存在属性“length”
  console.log(value.length)
  return value
}

id<string>('a')
  • Explanation: Type can represent any type, and there is no guarantee that there must be a length attribute. For example, the number type has no length
  • At this point, you need to add constraints to the generic type to shrink the type (narrow the value range of the type)

1. Add constraints

For example, if you want to access the length attribute of the parameter value, you can add the following constraints:

// 创建一个接口
// interface ILength { length: number }
type Length = {
    
     length: number }

// Type extends Length 添加泛型约束
// 解释:表示传入的 类型 必须满足 Length 类型的要求才行,也就是得有一个 number 类型的 length 属性
function id<Type extends Length>(value: Type): Type {
    
    
  console.log(value.length)
  return value
}
  • Create an interface ILength describing constraints that requires a length property
  • Use this interface with the extends keyword to add constraints to generics (type variables)
  • This constraint means: the type passed in must have a length property

Note: The actual parameter passed in (for example, an array) only needs to have a length attribute (type compatibility)

2. Multiple type variables

There can be multiple generic type variables, and the type variables can also be constrained (for example, the second type variable is constrained by the first type variable).
For example, create a function to get the value of the property in the object:

function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
    
    
  return obj[key]
}
let person = {
    
     name: 'jack', age: 18 }
getProp(person, 'name')
  1. Added a second type variable Key, used between two type variables, separated by commas
  2. The keyof keyword takes an object type and produces a union type of its key names (which may be strings or numbers)
  3. In this example, keyof Type actually gets the joint type of all keys of the person object, that is: 'name' | 'age'
  4. The type variable Key is constrained by Type, which can be understood as: Key can only be any one of all the keys of Type, or can only access the properties that exist in the object

Of course, the first parameter can also add type constraints, such as:

// Type extends object 表示: Type 应该是一个对象类型,如果不是 对象 类型,就会报错
function getProperty<Type extends object, Key extends keyof Type>(obj: Type, key: Key) {
    
    
  return obj[key]
}

9. Generic constraint case code

export {
    
    }

/*
function id<Type>(value: Type): Type {
  // 报错: 类型“Type”上不存在属性“length”
  // 原因:在此处,泛型类型 Type 是一个占位的类型,可以接收任意类型
  //      既然可以是任意的类型,那么,在访问属性的时候,就会提示所有类型都有的属性
  //      但又没有任何一个属性,在所有类型中都存在,所以,此处访问属性就报错了
  // 其中,最主要的原因是: Type 可以是任意类型
  console.log(value.length)
  return value
}
*/

// id<string>('a')
// id<null>(null)

// 实际上,在真实的项目开发中,函数、自定义类型等可以配合 泛型 来使用的
// 都会有明确的类型范围,而不是任意类型
//
// 比如,要访问 length 属性,那么,泛型类型就应该限制在有 length 的范围内

// 需求:要访问参数的 length 属性
// extends 关键字用来给 泛型添加类型约束
//  可以理解为:左侧的类型 必须满足 右侧类型的约束
// Type extends { length: number } 表示: Type 是一个对象,并且需要有 length 属性
function id1<Type extends {
    
     length: number }>(value: Type): Type {
    
    
  console.log(value.length)
  return value
}

// 错误: 因为 {} 没有 length 属性
// id1({})

id1({
    
     length: 1 })
id1({
    
     length: 1, name: '' })
id1([1, 3]) // 数组也是有 length 属性的
id1('abc') // 由于 JS 中可以 'abc'.length,也就是说只要能够访问 length 属性,那就是满足类型约束的

// 问题:'abc'.length 为什么 string 类型(简单类型),可以访问对象属性?
// 回答:简单类型在访问属性时,JS 会将其包装成复杂类型,比如,'abc' 就会被包装成:new String('abc')

export {
    
    }

// 泛型约束的示例:
// 需求:创建一个函数,函数的参数只能是 string 类型
function fn<Type extends string>(value: Type) {
    
    }

fn('abc')
// 错误演示:
// fn(1)

// 需求:创建一个函数,函数的参数只能是: 'name' | 'gender' 其中一个
function fn1<Type extends 'name' | 'gender'>(value: Type) {
    
    }
fn1('name')
fn1('gender')
// 错误演示:
// fn1('gender1')

// 需求:创建一个函数,实现访问对象中的属性。它有两个参数
//  参数1: 要访问的对象, 约定第一个参数必须是对象类型
//  参数2: 要访问的属性, 约定第二个参数只能是第一个对象中有的键
//  返回值: 要访问的属性的值
//
// 调用演示:
//  const obj = { name: 'jack', gender: 'male' }
//  const name = getValue(obj, 'name') // 'jack'
//  const gender = getValue(obj, 'gender') // 'male'

const obj = {
    
     name: 'jack', gender: 'male', age: 18 }
// 泛型可以有多个类型变量,使用 逗号 分隔
// Key 的类型应该是 Obj 对象中所有的键,也就是:对象 Obj 中有什么键,那么,Key 就是哪些键
// function getValue<Obj extends object, Key extends 'name' | 'gender'>(
//  keyof Obj 表示获取对象 Obj 中所有键的类型,如果对象中有多个属性,那么,就回到一个: 字面量类型+联合类型的形式
// 比如,此处,keyof Obj 结果是: 'name' | 'gender'
// Obj[Key] 表示对象 Obj 中所有 Key 的值的类型
function getValue<Obj extends object, Key extends keyof Obj>(
  o: Obj,
  k: Key
): Obj[Key] {
    
    
  return o[k]
}

console.log(getValue(obj, 'name'))
console.log(getValue(obj, 'gender'))
console.log(getValue(obj, 'age'))

// getValue(obj, 'gend123er')
// getValue(undefined)

type Obj = typeof obj // { name: string; gender: string; age: number }
type K = keyof Obj // "name" | "gender" | "age"
type V = Obj[K] // string | number

Guess you like

Origin blog.csdn.net/m0_62181310/article/details/126851989