高级类型(不涉及到泛型)
联合类型
一个变量可以支持多种类型,极大提高了类型的可扩展性
let num: number | string
num = 1
num = 'one'
使用变量方法时,只能使用几种属性共有的方法
如果使用了共有属性以外的方法,就会报错,例如:直接访问 length 属性,string 类型上有,number 类型上没有,就报错了。
交叉类型
要对对象形状进行扩展,可以使用交叉类型 &
interface Person {
name: string
age: number
}
type Student = Person & { grade: number }
const student: Student = {
name: 'Alice',
age: 18,
grade: 12
};
console.log(student)
结果:
类型保护
function getLength(arg: number | string): number {
return arg.length
}
因为 number 类型上没有 length 属性,所以要报错
解决方法:使用 typeof 关键字判断变量的类型,最后都得到 number 类型的值
function getLength(arg:number | string): number {
if(typeof arg === 'number'){
return arg
}else{
return arg.toString().length
}
}
字面量类型
type Sex = '男' | '女'
定义好了字面量类型值,不能赋定义以外的值
泛型
泛型是指在定义函数、接口或类的时候,不预先指定具体类型,而是在使用的时候再指定类型。
以现在知道的知识,有一些问题是很难解决的,例如下面的例子:
定义一个 print 函数,这个函数的功能是把传入的参数打印出来,再返回这个参数,传入参数的类型是 string,函数返回类型为 string。
function print(arg:string):string {
console.log(arg)
return arg
}
现在需求变了,我还需要打印 number 类型,怎么办?
可以使用联合类型来改造:
function print(arg:string | number):string | number {
console.log(arg)
return arg
}
现在需求又变了,我还需要打印 string 数组、number 数组,甚至任何类型,怎么办?
有个笨方法,支持多少类型就写多少联合类型。
或者把参数类型改成 any。
function print(arg:any):any {
console.log(arg)
return arg
}
const res:string = print(123)
且不说写 any 类型不好,毕竟在 TS 中尽量不要写 any,而且这也不是我们想要的结果,只能说传入的值是 any 类型,输出的值是 any 类型,传入和返回并不是统一的。
泛型就可以轻松解决输入输出要一致的问题,但是泛型不止能解决这个问题,还有很多问题都可以用泛型来解决
泛型变量
也就是说使用大写字母 A-Z 定义的类型变量都属于泛型,把 T 换成 A,也是一样的
常见的泛型变量代表
- T(Type):表示一个 TypeScript 类型
- K(Key):表示对象中的键类型
- V(Value):表示对象中的值类型
- E(Element):表示元素类型
泛型基本使用
泛型的语法是 <> 里写类型参数,一般可以用 T 来表示
解决上面的问题
function print<T>(arg:T):T {
console.log(arg)
return arg
}
const res:number = print(123)
这样,就实现了输入和输出的类型统一,且可以输入输出任何类型。
泛型中的 T 就像一个占位符、或者说一个变量,在使用的时候可以把定义的类型像参数一样传入,它可以原封不动地输出。
使用的时候可以有两种方式指定类型:
- 定义要使用的类型
- TS 类型推断,自动推导出类型
// 定义要使用的类型
function print<T>(arg:T):T {
console.log(arg)
return arg
}
const res = print<number>(123)
// TS 类型推断,自动推导出类型
function print<T>(arg:T):T {
console.log(arg)
return arg
}
const res = print(123)
console.log(typeof res === 'number') // true
自动推导类型,结果:
如果变量类型用到接口
interface Iprint<T> {
(arg: T): T
}
function print<T>(arg: T): T {
console.log(arg)
return arg
}
const myPrint: Iprint<number> = print<number>
const bb = myPrint(123)
console.log(typeof bb === 'number') // true
默认参数
// 默认为 number 类型
interface Iprint<T = number> {
(arg: T): T
}
function print<T>(arg:T) {
console.log(arg)
return arg
}
const myPrint: Iprint = print
const bb = myPrint(123)
console.log(typeof bb === 'number') // true
处理多个函数参数
现在函数接收到一个元组,元组中有两项数据,交换元组的两项数据,并返回该元组
function swap(tuple) {
return [tuple[1], tuple[0]]
}
如果这样写的话,没有定义元素类型,默认用 any 类型
用泛型进行解决问题
function swap<T, U>(tuple: [T, U]): [U, T]{
return [tuple[1], tuple[0]]
}
const a = swap(['lll',123])
传入的参数里,第 0 项为 string 类型,第 1 项为 number 类型。
在返回值里,第 0 项为 number 类型,第 1 项为 string 类型。
第 0 项上全是 number 的方法。
第 1 项上全是 string 的方法。
约束泛型
现在有一个函数,功能是打印传入参数的长度
function printLength<T>(arg: T): T {
console.log(arg.length)
return arg
}
不确定 T 是否有 length 属性,肯定会报错的
这里可以和 interface 结合,来约束类型。
interface ILength {
length: number
}
function printLength<T extends ILength>(arg: T): T {
console.log(arg.length)
return arg
}
关键就是
<T extends ILength>,让这个泛型继承接口ILength。
传入的参数,必须有 length 属性,没有就会报错
泛型约束类
定义一个栈,有入栈和出栈两个方法,如果想入栈和出栈的元素类型统一,就可以这么写:
class Stack<T> {
private data: T[] = []
push(item:T) {
return this.data.push(item)
}
pop():T | undefined {
return this.data.pop()
}
}
在定义实例的时候写类型,比如,入栈和出栈都要是 number 类型,就这么写:
const s1 = new Stack<number>()
如果入栈的是一个字符串,直接报错
如果需求变了,想要入栈一个字符串类型的值
const s1 = new Stack<string>()
注意:泛型无法约束类的静态成员
例如:给pop 方法定义 static 关键字,将 pop 方法变成一个静态方法,就报错了
泛型约束接口
使用泛型,也可以对 interface 进行改造,让 interface 更灵活。
interface IKeyValue<T, U> {
key: T
value: U
}
const k1:IKeyValue<number, string> = { key: 18, value: 'lin'}
const k2:IKeyValue<string, number> = { key: 'lin', value: 18}
泛型定义数组
不用泛型
const arr: number[] = [1,2,3]
用泛型
const arr: Array<number> = [1,2,3]
高级类型(二)
索引类型
从对象中抽取一些属性的值,然后拼接成数组
const userInfo = {
name: 'lin',
age: '18',
}
function getValues(userInfo: any, keys: string[]) {
return keys.map(key => userInfo[key])
}
// 抽取指定属性的值
console.log(getValues(userInfo, ['name','age'])) // ['lin', '18']
// 抽取obj中没有的属性:
console.log(getValues(userInfo, ['sex','outlook'])) // [undefined, undefined]
虽然 obj 中并不包含 sex 和 outlook 属性,但 TS 编译器并未报错
映射类型
TS允许将一个类型映射成另外一个类型。
为了方便开发者 TypeScript 内置了一些常用的工具类型,比如 Partial、Required、Readonly、Record 和 ReturnType 等,下面出现了很多工具类型
in
介绍映射类型之前,先介绍一下 in 操作符,用来对联合类型实现遍历。
type Person = "name" | "school" | "major"
type Obj = {
[p in Person]: string
}
Partial
Partial<T>将T的所有属性映射为可选的,例如:
interface IPerson {
name: string
age: number
}
let p1: IPerson = {
name: 'lin',
age: 18
}
使用了 IPerson 接口,就一定要传 name 和 age 属性,
使用 Partial 改造一下,就可以变成可选属性,
interface IPerson {
name: string
age: number
}
type IPartial = Partial<IPerson>
let p1: IPartial = {}
Partial 原理
Partial 的实现用到了 in 和 keyof
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P]
}
[P in keyof T]遍历T上的所有属性?:设置属性为可选的T[P]设置类型为原来的类型
Readonly
Readonly<T>将T的所有属性映射为只读的,例如:
interface IPerson {
name: string
age: number
}
type IReadOnly = Readonly<IPerson>
let p1: IReadOnly = {
name: 'lin',
age: 18
}
Readonly 原理
和 Partial 几乎完全一样,
/**
* Make all properties in T readonly
*/
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}
[P in keyof T]遍历T上的所有属性readonly设置属性为只读的T[P]设置类型为原来的类型
Pick
Pick用于抽取对象子集,挑选一组属性并组成一个新的类型,例如:
interface IPerson {
name: string
age: number
sex: string
}
type IPick = Pick<IPerson, 'name' | 'age'>
let p1: IPick = {
name: 'lin',
age: 18
}
这样就把 name 和 age 从 IPerson 中抽取出来。
Pick 原理
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P]
}
Pick映射类型有两个参数:
- 第一个参数T,表示要抽取的目标对象
- 第二个参数K,具有一个约束:K一定要来自T所有属性字面量的联合类型
Record
上面三种映射类型官方称为同态,意思是只作用于 obj 属性而不会引入新的属性。
Record 是会创建新属性的非同态映射类型。
interface IPerson {
name: string
age: number
}
type IRecord = Record<string, IPerson>
let personMap: IRecord = {
person1: {
name: 'lin',
age: 18
},
person2: {
name: 'liu',
age: 25
}
}
Record 原理
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T
}
Record 映射类型有两个参数:
- 第一个参数可以传入继承于 any 的任何值
- 第二个参数,作为新创建对象的值,被传入。
条件类型
T extends U ? X : Y
//若类型 T 可被赋值给类型 U,那么结果类型就是 X 类型,否则就是 Y 类型
Exclude 和 Extract 的实现就用到了条件类型。
Exclude
Exclude 意思是不包含,Exclude<T, U> 会返回 联合类型 T 中不包含 联合类型 U 的部分。
type Test = Exclude<'a' | 'b' | 'c', 'a'>
Exclude 原理
/**
* Exclude from T those types that are assignable to U
*/
type Exclude<T, U> = T extends U ? never : T
never表示一个不存在的类型never与其他类型的联合后,为其他类型
type Test = string | number | never
Extract
Extract<T, U>提取联合类型 T 和联合类型 U 的所有交集。
type Test = Extract<'key1' | 'key2', 'key1'>
Extract 原理
/**
* Extract from T those types that are assignable to U
*/
type Extract<T, U> = T extends U ? T : never
工具类型
索引类型、映射类型和条件类型都是工具类型。
下面在做一个补充
Omit
Omit<T, U>从类型 T 中剔除 U 中的所有属性。
interface IPerson {
name: string
age: number
}
type IOmit = Omit<IPerson, 'age'>
这样就剔除了 IPerson 上的 age 属性。
Omit 原理
/**
* Construct a type with the properties of T except for those in type K.
*/
type Omit<T, K extends keyof any> =
Pick<T, Exclude<keyof T, K>>
Pick用于挑选一组属性并组成一个新的类型,Omit 是剔除一些属性,留下剩余的,他们俩有点相反的感觉。
那么就可以用 Pick 和 Exclude 实现 Omit。
当然也可以不用 Pick 实现,
type Omit2<T, K extends keyof any> = {
[P in Exclude<keyof T, K>]: T[P]
}
NonNullable
NonNullable<T> 用来过滤类型中的 null 及 undefined 类型。
type T0 = NonNullable<string | number | undefined>; // string | number
type T1 = NonNullable<string[] | null | undefined>; // string[]
NonNullable 原理
/**
* Exclude null and undefined from T
*/
type NonNullable<T> =
T extends null | undefined ? never : T
never表示一个不存在的类型never与其他类型的联合后,为其他类型
Parameters
Parameters 获取函数的参数类型,将每个参数类型放在一个元组中。
type T1 = Parameters<() => string> // []
type T2 = Parameters<(arg: string) => void> // [string]
type T3 = Parameters<(arg1: string, arg2: number) => void> // [arg1: string, arg2: number]
Parameters 原理
/**
* Obtain the parameters of a function type in a tuple
*/
type Parameters<T extends (...args: any) => any> =
T extends (...args: infer P) => any ? P : never
在条件类型语句中,可以用 infer 声明一个类型变量并且对它进行使用。
Parameters首先约束参数T必须是个函数类型- 判断
T是否是函数类型,如果是则使用infer P暂时存一下函数的参数类型,后面的语句直接用 P 即可得到这个类型并返回,否则就返回never
ReturnType
ReturnType 获取函数的返回值类型。
type T0 = ReturnType<() => string> // string
type T1 = ReturnType<(s: string) => void> // void
ReturnType 原理
/**
* Obtain the return type of a function type
*/
type ReturnType<T extends (...args: any) => any> =
T extends (...args: any) => infer R ? R : any
懂了 Parameters,也就懂了 ReturnType,
ReturnType首先约束参数T必须是个函数类型- 判断
T是否是函数类型,如果是则使用infer R暂时存一下函数的返回值类型,后面的语句直接用 R 即可得到这个类型并返回,否则就返回any