我们来提一个需求:封装一个函数,传入一个参数,并且返回这个参数;
function foo(arg: number): number{
return arg
}
//虽然any是可以的,但是定义为any的时候,我们其实已经丢失了类型信息
function foo(arg: any): any{
return arg
}
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)
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")
定义接口时也可以使用泛型
//定义接口
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)
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({})
在泛型约束中使用类型参数: 你可以声明一个类型参数,这个类型参数被其他类型参数约束;
//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
有的时候,一个类型需要基于另外一个类型,但是你又不想拷贝一份,这个时候可以考虑使用映射类型。
大部分内置的工具都是通过映射类型来实现的;
大多数类型体操的题目也是通过映射类型完成的;
//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>