TypeScript -- 泛型

1,036 阅读4分钟

看完本篇文章,自己组织语言回答以下三个问题:

  1. 泛型是什么?
  2. 我们为什么需要用泛型?
  3. 使用泛型典型的应用场景?

泛型<>

泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用

本质: 参数化类型,通俗的讲,就是所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和函数的创建中,分别成为泛型类泛型接口泛型函数。可以通过以下代码理解泛型的本质

// 创建一个 函数,传入什么数据就返回该数据本身(也就是说,参数和返回值类型相同)
function fn(value: number): number { return value }

1. 泛型函数

语法: 在函数名称的后面添加 <>(尖括号),尖括号中指定具体的类型

  // 泛型函数 ====> 定义一个带泛型变量的函数
  // 类型变量 T,是一种特殊类型的变量,它处理类型而不是值
  function id<T>(value:T) : T{
    return value
  }
  // 1.1 使用: 尖括号中指定具体的类型
  const r1 = id<number>(1)
  const r2 = id<boolean>(false)
  // 1.2 简化-类型推断
  const r3 = id('abc') // String
  
  // 这样,通过泛型就做到了让 id 函数与多种不同的类型一起工作,实现了复用的同时保证了类型安全

2. 泛型约束

为什么需要泛型约束?

答: 默认情况下,泛型函数的类型变量 Type 可以代表多个类型,这导致无法访问任何属性,因为其无法保证传入的类型一定存在某个属性。 比如 length 属性,number 类型就没有 length,就需要为泛型添加约束来收缩类型(缩窄类型取值范围)

添加泛型约束的方法: 1 指定更加具体的类型 ;2 添加约束。

2.1 指定更加具体的类型

// 我们这里使用方法也是拿lenght属性来举例

// 将类型修改为 Type[](Type 类型的数组),因为只要是数组就一定存在 length 属性,因此就可以访问了
function id<Type>(value: Type[]): Type[] {
  console.log(value.length)
  return value
}

2.2 添加约束

// 我们这里使用方法也是拿lenght属性来举例

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

// Type extends ILength 添加泛型约束
// 该约束表示:传入的类型必须具有 length 属性。比如数组
function id<Type extends ILength>(value: Type): Type {
  console.log(value.length)
  return value
}

3. 泛型类型变量

  • 可以有多个
  • 并且类型变量之间还可以约束(比如,第二个类型变量受第一个类型变量约束)
  // 多个类型变量 - 相互约束
  // keyof 关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型
  // 比如: 第二个类型受第一个变量的约束 Key extends keyof Type 代表继承 keyof Type
  // keyof Type 是 Type的所有 key值
  
  let obj = { name: 'jack', age: 18 }
  // getProp( 对象, 属性名 ) ===> 属性值
  // 本示例中 keyof Type 实际上获取的是 obj 对象所有键的联合类型,也就是:'name' | 'age'
  function getProp<Type, Key extends keyof Type>(obj: Type, key: Key) {
    return obj[key]
  }
  getProp(obj, 'name') 
  // 因为 obj 是一个对象,所以这里的 Type 类型为对象类型

4. 泛型接口

  interface IdFunc<Type> {
    id: (value: Type) => Type
    ids: () => Type[]
  }
  let obj: IdFunc<number> = {
    id(value) { return value },
    ids() { return [1, 3, 5] }
  }

  // 场景: 定义数组类型时
  // 方法一:
  let arr1: number[] = [1, 2, 3]
  // 方法二:
  // 这里的 Array 就是泛型接口
  let arr2: Array<string> = ['1', '2', '3']

5. 泛型练习

  • 最后来一个泛型小练习检测一下泛型学习的成果: 下面是题目要求以及要改写的代码
    
// 1. 题目 - useState它接收一个任意类型的数据,返回一个数组。
/** 要求:
 *  数组的第一个元素的类型与入参一致; 
 *  数组的第二个元素是一个函数
 *  第二个元素函数的入参类型和返回值类型与useState的入参一致 
 */

// 2. 将以下的代码改写成符合上述要求的 Ts 代码
function useState(value) {
  const setValue = () => {
  }
  return [value, setValue]
} 
// 测试代码 - 鼠标悬停查看结果
const [num, setNum] = useState(0)
const [str, setStr] = useState('ab')
  • 下面是上述代码的答案:自己思考后再看答案,鼠标悬停在测试代码上可以看到符合题目要求的结果
function useState<Type>(value:Type): [Type, (val: Type)=> Type] {
  const setValue = (val: Type): Type => {
    return val
  }
  return [value, setValue]
}
// 测试代码 - 鼠标悬停查看结果
const [str, setStr] = useState('123')
const [num, setNum] = useState(123)

总结

本人回答一下文章开头的三个问题,有不同意见的小伙伴可以在评论中发表自己的意见

  1. 泛型是什么?

    答: 泛型的本质是参数化类型 ,就是把类型当做参数使用,代码里面就是<>

  2. 我们为什么需要用泛型?

    答:当我们在定义函数时,不知道其传入的参数是什么类型的,我们就可以用泛型去定义它,传入参数是什么类型泛型就是什么

  3. 使用泛型典型的应用场景?

    答:使用泛型典型的应用场景有 泛型类,泛型接口、泛型函数

    我就拿泛型接口举个例子:当我们需要创建一个参数为字符串的数组时,我们就可以使用泛型接口 Array<string> 这里Array就是泛型接口