看完本篇文章,自己组织语言回答以下三个问题:
- 泛型是什么?
- 我们为什么需要用泛型?
- 使用泛型典型的应用场景?
泛型<>
泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用
本质: 参数化类型
,通俗的讲,就是所操作的数据类型被指定为一个参数,这种参数类型可以用在类、接口和函数的创建中,分别成为泛型类,泛型接口、泛型函数。可以通过以下代码理解泛型的本质
// 创建一个 函数,传入什么数据就返回该数据本身(也就是说,参数和返回值类型相同)
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)
总结
本人回答一下文章开头的三个问题,有不同意见的小伙伴可以在评论中发表自己的意见
-
泛型是什么?
答: 泛型的本质是参数化类型 ,就是把类型当做参数使用,代码里面就是
<>
-
我们为什么需要用泛型?
答:当我们在定义函数时,不知道其传入的参数是什么类型的,我们就可以用泛型去定义它,传入参数是什么类型泛型就是什么
-
使用泛型典型的应用场景?
答:使用泛型典型的应用场景有 泛型类,泛型接口、泛型函数
我就拿泛型接口举个例子:当我们需要创建一个参数为字符串的数组时,我们就可以使用泛型接口
Array<string>
这里Array就是泛型接口