一 类型参数化
我们来提一个需求:封装一个函数,传入一个参数,并且返回这个参数;
- 如果我们是TypeScript的思维方式,要考虑这个参数和返回值的类型需要一致:
function foo(arg: number): number{
return arg
}
- 注意:上面的代码虽然实现了,但是不适用于其他类型,比如string,boolean等类型
//虽然any是可以的,但是定义为any的时候,我们其实已经丢失了类型信息
function foo(arg: any): any{
return arg
}
- 我们需要在这里使用一种特性的变量 - 类型变量(type variable),它作用于类型,而不是值:
- 基本使用
function foo<Type>(arg: Type): Type {
return arg
}
//完整写法-通过<类型>的方式将类型传递给函数
//res1的类型是number
const res1 = foo<number>(123)
//res2的类型是string
const res2 = foo<string>("kobe")
//res3的类型是{name: string}
const res3 = foo<{name: string}>({name: "jack"})
- 类型推导:自动推导出我们传入变量的类型
function foo<Type>(arg: Type): Type {
return arg
}
//省略写法
//res4经过类型推导后是123(字面量类型),const推导的会更具体
const res4 = foo(123)
//res5经过类型推导后是number
let res5 = foo(123)
- useState的泛型实现
function useState<Type>(initialState: Type): [Type, (newState: Type) => void] {
let state = initialState
function setState(newState: Type) {
state = newState
}
return [state, setState]
}
//省略写法
const [count,setCount] = useState(123)
const [message, setMessage] = useState("kobe")
//此时传入一个空字符串,ts类型推导不正确,所以最好指定一下类型
const [banners, setBanners] = useState<any[]>([])
- 传入多个类型
function foo<Type, Element>(arg1: Type, arg2: Element) {
return arg
}
foo(123, 321)
foo(123, "abc")
- 常用缩写
- T: Type的缩写,类型
- K、V: key和value的缩写, 键值对
- E:Element的缩写, 元素
- O:Object的缩写,对象
二 泛型接口
定义接口时也可以使用泛型
- 基本使用
//定义接口
interface personType<Type> {
name: string
age: Type
height: Type
}
//personType的类型是number
const Person:personType<number> = {
name: "jack",
age: 19,
height: 189
}
//personType的类型是string
const Person:personType<string> = {
name: "jack",
age: "19",
height: "189"
}
- 接口类型有默认值
//定义接口,定义接口类型默认是number类型
interface personType<Type = number> {
name: string
age: Type
height: Type
}
//personType的类型是默认类型:number
const Person:personType = {
name: "jack",
age: 19,
height: 189
}
三 泛型类
class Point<Type = number> {
x: Type
y: Typr
constructor(x: Type, y: Type) {
this.x = x
this.y = y
}
}
const p1 = new Point(10, 20)
//使用默认类型number类型,x为number类型
console.log(p1.x)
const p2 = new Point("10", "20")
//自动进行类型推导为string类型,所以x为string类型
console.log(p2.x)
四 泛型约束
- 有时候我们希望传入的类型有某些共性,但是这些共性可能不是在同一种类型中:
- 比如string和array都是有length的,或者某些对象也是会有length属性的;
- 那么只要是拥有length的属性都可以作为我们的参数类型。
interface ILength {
length: number
}
//方法一:可以实现获取字符串,数组,对象的长度,但length1,2,3丢失了类型,都是ILength类型
function getLength(arg: ILenght) {
return arg.length
}
const length1 = getLength("abc")
const length2 = getLength(["aa", "bb", "cc"])
const length2 = getLength({ length: 100 })
//方法二:获取传入的内容,这个内容必须有length属性,也可以有其他属性,但是必须至少有这个成员。
//Type相当于是一个变量,用于记录本次调用的类型,所以在整个函数的执行周期中,一直保留着参数info1,2,3的类型
function getInfo<Type extends ILength>(args: Type): Type {
return args
}
const info1 = getInfo("abc")
const info2 = getInfo(["aa", "bb", "cc"])
const info3 = getInfo({ length: 100 })
//错误,因为参数都没有length属性
const info4 = getInfo(12345)
const info5 = getInfo({})
五 泛型参数使用约束
在泛型约束中使用类型参数: 你可以声明一个类型参数,这个类型参数被其他类型参数约束;
- 举个栗子:我们希望获取一个对象给定属性名的值
- 我们需要确保我们不会获取 obj 上不存在的属性;
- 所以我们在两个类型之间建立一个约束;
//K extends keyof O得到的是obj所有key的联合类型: "name"|"age"|"height"
function getProperty<O, K extends keyof O>(obj: O, key: K) {
return obj[key]
}
const info = {
name: "jack",
age: 18,
height: 1.89
}
const name = getProperty(info, "name") //正确
const address = getProperty(info, "address") //错误,因为address不是info的其中一个key
六 映射类型
有的时候,一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型。
大部分内置的工具都是通过映射类型来实现的;
大多数类型体操的题目也是通过映射类型完成的;
- 映射类型建立在索引签名的语法上:
- 映射类型,就是使用了 PropertyKeys 联合类型的泛型;
- 其中 PropertyKeys 多是通过 keyof 创建,然后循环遍历键名创建一个类型;
- 基本使用
//TS提供了映射类型:函数
//Type = Iperson
//keyof = "name"|"age"
type MapPerson<Type> {
//索引类型依次进行使用:
[property in keyof Type]: Type[property]
//name : string
//age : number
}
interface IPerson {
name: string
age: number
}
type NewPerson = MapPerson<IPerson>
- 映射修饰符使用
在使用映射类型时,有两个额外的修饰符可能会用到:
一个是 readonly,用于设置属性只读;
一个是 ? ,用于设置属性可选;
type MapPerson<Type> {
//设置属性可选和属性只读
readonly [property in keyof Type]?: Type[property]
//readonly name ?: string
//readonly age ?: number
}
interface IPerson {
name: string
age: number
}
type IPersonOptional = MapPerson<IPerson>
- 修饰符符号
你可以通过前缀 - 或者 + 删除或者添加这些修饰符,如果没有写前缀,相当于使用了 + 前缀。
type MapPerson<Type> {
//设置属性可选和属性只读
+readonly [property in keyof Type]+?: Type[property]
//readonly name ?: string
//readonly age ?: number
-readonly [property in keyof Type]-?: Type[property]
//name : string
//age : number
}
interface IPerson {
name: string
age: number
}
type IPersonOptional = MapPerson<IPerson>