TypeScript 高级类型及用法
一、高级类型
交叉类型(&)
交叉类型是将多个类型合并为一个类型。这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。
语法:
T & U
其返回类型既要符合
T
类型也要符合U
类型用法:假设有两个接口:一个是
Ant
蚂蚁接口,一个是Fly
飞翔接口,现在有一只会飞的蚂蚁:
interface Ant {name: string;weight: number;
}interface Fly {flyHeight: number;speed: number;
}// 少了任何一个属性都会报错
const flyAnt: Ant & Fly = {name: '蚂蚁呀嘿',weight: 0.2,flyHeight: 20,speed: 1,
};
复制代码
联合类型(|)
联合类型与交叉类型很有关联,但是使用上却完全不同。
语法:
T | U
其返回类型为连接的多个类型中的任意一个
用法:假设声明一个数据,既可以是
string
类型,也可以是number
类型
let stringOrNumber: string | number = 0stringOrNumber = ''
复制代码
再看下面这个例子,start
函数的参数类型既是 Bird | Fish
,那么在 start
函数中,想要直接调用的话,只能调用 Bird
和 Fish
都具备的方法,否则编译会报错
class Bird {fly() {console.log('Bird flying');}layEggs() {console.log('Bird layEggs');}
}class Fish {swim() {console.log('Fish swimming');}layEggs() {console.log('Fish layEggs');}
}const bird = new Bird();
const fish = new Fish();function start(pet: Bird | Fish) {// 调用 layEggs 没问题,因为 Bird 或者 Fish 都有 layEggs 方法pet.layEggs();// 会报错:Property 'fly' does not exist on type 'Bird | Fish'// pet.fly();// 会报错:Property 'swim' does not exist on type 'Bird | Fish'// pet.swim();
}start(bird);start(fish);
复制代码
二、关键字
类型约束(extends)
语法:T extends K
这里的 extends 不是类、接口的继承,而是对于类型的判断和约束,意思是判断 T 能否赋值给 K
可以在泛型中对传入的类型进行约束
const copy = (value: string | number): string | number => value// 只能传入 string 或者 number
copy(10)// 会报错:Argument of type 'boolean' is not assignable to parameter of type 'string | number'
// copy(false)
复制代码
也可以判断 T 是否可以赋值给 U,可以的话返回 T,否则返回 never
type Exclude<T, U> = T extends U ? T : never;
复制代码
类型映射(in)
会遍历指定接口的 key 或者是遍历联合类型
interface Person {name: stringage: numbergender: number
}// 将 T 的所有属性转换为只读类型
type ReadOnlyType<T> = {readonly [P in keyof T]: T
}// type ReadOnlyPerson = {
// readonly name: Person;
// readonly age: Person;
// readonly gender: Person;
// }
type ReadOnlyPerson = ReadOnlyType<Person>
复制代码
类型谓词(is)
语法:
parameterName is Type
parameterName
必须是来自于当前函数签名里的一个参数名,判断 parameterName 是否是 Type 类型。
具体的应用场景可以跟着下面的代码思路进行使用:
看完联合类型的例子后,可能会考虑:如果想要在 start
函数中,根据情况去调用 Bird
的 fly
方法和 Fish
的 swim
方法,该如何操作呢?
首先想到的可能是直接检查成员是否存在,然后进行调用:
function start(pet: Bird | Fish) {// 调用 layEggs 没问题,因为 Bird 或者 Fish 都有 layEggs 方法pet.layEggs();if ((pet as Bird).fly) {(pet as Bird).fly();} else if ((pet as Fish).swim) {(pet as Fish).swim();}
}
复制代码
但是这样做,判断以及调用的时候都要进行类型转换,未免有些麻烦,可能会想到写个工具函数判断下:
function isBird(bird: Bird | Fish): boolean {return !!(bird as Bird).fly;
}function isFish(fish: Bird | Fish): boolean {return !!(fish as Fish).swim;
}function start(pet: Bird | Fish) {// 调用 layEggs 没问题,因为 Bird 或者 Fish 都有 layEggs 方法pet.layEggs();if (isBird(pet)) {(pet as Bird).fly();} else if (isFish(pet)) {(pet as Fish).swim();}
}
复制代码
看起来简洁了一点,但是调用方法的时候,还是要进行类型转换才可以,否则还是会报错,那有什么好的办法,能让我们判断完类型之后,就可以直接调用方法,不用再进行类型转换呢?
OK,肯定是有的,类型谓词 is
就派上用场了
用法:
function isBird(bird: Bird | Fish): bird is Bird {return !!(bird as Bird).fly
}function start(pet: Bird | Fish) {// 调用 layEggs 没问题,因为 Bird 或者 Fish 都有 layEggs 方法pet.layEggs();if (isBird(pet)) {pet.fly();} else {pet.swim();}
};
复制代码
每当使用一些变量调用 isFish
时,TypeScript
会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。
TypeScript 不仅知道在 if 分支里 pet 是 Fish 类型;它还清楚在 else 分支里,一定不是 Fish 类型,一定是 Bird 类型
待推断类型(infer)
可以用 infer P
来标记一个泛型,表示这个泛型是一个待推断的类型,并且可以直接使用
比如下面这个获取函数参数类型的例子:
type ParamType<T> = T extends (param: infer P) => any ? P : T;type FunctionType = (value: number) => booleantype Param = ParamType<FunctionType>; // type Param = numbertype OtherParam = ParamType<symbol>; // type Param = symbol
复制代码
判断 T 是否能赋值给 (param: infer P) => any
,并且将参数推断为泛型 P,如果可以赋值,则返回参数类型 P,否则返回传入的类型
再来一个获取函数返回类型的例子:
type ReturnValueType<T> = T extends (param: any) => infer U ? U : T;type FunctionType = (value: number) => booleantype Return = ReturnValueType<FunctionType>; // type Return = booleantype OtherReturn = ReturnValueType<number>; // type OtherReturn = number
复制代码
判断 T 是否能赋值给 (param: any) => infer U
,并且将返回值类型推断为泛型 U,如果可以赋值,则返回返回值类型 P,否则返回传入的类型
原始类型保护(typeof)
语法:
typeof v === "typename"
或typeof v !== "typename"
用来判断数据的类型是否是某个原始类型(
number
、string
、boolean
、symbol
)并进行类型保护
"typename"必须是 "number", "string", "boolean"或 "symbol"。但是 TypeScript 并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。
看下面这个例子, print
函数会根据参数类型打印不同的结果,那如何判断参数是 string
还是 number
呢?
function print(value: number | string) {// 如果是 string 类型// console.log(value.split('').join(', '))// 如果是 number 类型// console.log(value.toFixed(2))
}
复制代码
有两种常用的判断方式:
根据是否包含
split
属性判断是string
类型,是否包含toFixed
方法判断是number
类型弊端:不论是判断还是调用都要进行类型转换
使用类型谓词
is
弊端:每次都要去写一个工具函数,太麻烦了
用法:这就到了
typeof
一展身手的时候了
function print(value: number | string) {if (typeof value === 'string') {console.log(value.split('').join(', '))} else {console.log(value.toFixed(2))}
}
复制代码
使用 typeof 进行类型判断后,TypeScript 会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。
类型保护(instanceof)
与 typeof
类似,不过作用方式不同,instanceof
类型保护是通过构造函数来细化类型的一种方式。
instanceof
的右侧要求是一个构造函数,TypeScript
将细化为:
此构造函数的
prototype
属性的类型,如果它的类型不为any
的话构造签名所返回的类型的联合
还是以 类型谓词 is 示例中的代码做演示:
最初代码:
function start(pet: Bird | Fish) {// 调用 layEggs 没问题,因为 Bird 或者 Fish 都有 layEggs 方法pet.layEggs();if ((pet as Bird).fly) {(pet as Bird).fly();} else if ((pet as Fish).swim) {(pet as Fish).swim();}
}
复制代码
使用 instanceof
后的代码:
function start(pet: Bird | Fish) {// 调用 layEggs 没问题,因为 Bird 或者 Fish 都有 layEggs 方法pet.layEggs();if (pet instanceof Bird) {pet.fly();} else {pet.swim();}
}
复制代码
可以达到相同的效果
索引类型查询操作符(keyof)
语法:
keyof T
对于任何类型
T
,keyof T
的结果为T
上已知的 公共属性名 的 联合
interface Person {name: string;age: number;
}type PersonProps = keyof Person; // 'name' | 'age'
复制代码
这里,keyof Person
返回的类型和 'name' | 'age' 联合类型是一样,完全可以互相替换
用法:
keyof
只能返回类型上已知的 公共属性名
class Animal {type: string;weight: number;private speed: number;
}type AnimalProps = keyof Animal; // "type" | "weight"
复制代码
例如我们经常会获取对象的某个属性值,但是不确定是哪个属性,这个时候可以使用 extends
配合 typeof
对属性名进行限制,限制传入的参数只能是对象的属性名
const person = {name: 'Jack',age: 20
}function getPersonValue<T extends keyof typeof person>(fieldName: keyof typeof person) {return person[fieldName]
}const nameValue = getPersonValue('name')
const ageValue = getPersonValue('age')// 会报错:Argument of type '"gender"' is not assignable to parameter of type '"name" | "age"'
// getPersonValue('gender')
复制代码
索引访问操作符(T[K])
语法:
T[K]
类似于
js
中使用对象索引的方式,只不过js
中是返回对象属性的值,而在ts
中返回的是T
对应属性 P 的类型
用法:
interface Person {name: stringage: numberweight: number | stringgender: 'man' | 'women'
}type NameType = Person['name'] // stringtype WeightType = Person['weight'] // string | numbertype GenderType = Person['gender'] // "man" | "women"
复制代码
三、映射类型
只读类型(Readonly<T>
)
定义:
type Readonly<T> = {readonly [P in keyof T]: T[P];
}
复制代码
用于将
T
类型的所有属性设置为只读状态。
用法:
interface Person {name: stringage: number
}const person: Readonly<Person> = {name: 'Lucy',age: 22
}// 会报错:Cannot assign to 'name' because it is a read-only property
person.name = 'Lily'
复制代码
readonly
只读, 被readonly
标记的属性只能在声明时或类的构造函数中赋值,之后将不可改(即只读属性)
只读数组(ReadonlyArray<T>
)
定义:
interface ReadonlyArray<T> {/** Iterator of values in the array. */[Symbol.iterator](): IterableIterator<T>;/*** Returns an iterable of key, value pairs for every entry in the array*/entries(): IterableIterator<[number, T]>;/*** Returns an iterable of keys in the array*/keys(): IterableIterator<number>;/*** Returns an iterable of values in the array*/values(): IterableIterator<T>;
}
复制代码
只能在数组初始化时为变量赋值,之后数组无法修改
使用:
interface Person {name: string
}const personList: ReadonlyArray<Person> = [{ name: 'Jack' }, { name: 'Rose' }]// 会报错:Property 'push' does not exist on type 'readonly Person[]'
// personList.push({ name: 'Lucy' })// 但是内部元素如果是引用类型,元素自身是可以进行修改的
personList[0].name = 'Lily'
复制代码
可选类型(Partial<T>
)
用于将 T
类型的所有属性设置为可选状态,首先通过 keyof T
,取出类型 T
的所有属性, 然后通过 in
操作符进行遍历,最后在属性后加上 ?
,将属性变为可选属性。
定义:
type Partial<T> = {[P in keyof T]?: T[P];
}
复制代码
用法:
interface Person {name: stringage: number
}// 会报错:Type '{}' is missing the following properties from type 'Person': name, age
// let person: Person = {}// 使用 Partial 映射后返回的新类型,name 和 age 都变成了可选属性
let person: Partial<Person> = {}person = { name: 'pengzu', age: 800 }person = { name: 'z' }person = { age: 18 }
复制代码
必选类型(Required<T>
)
和 Partial
的作用相反
用于将 T
类型的所有属性设置为必选状态,首先通过 keyof T
,取出类型 T
的所有属性, 然后通过 in
操作符进行遍历,最后在属性后的 ?
前加上 -
,将属性变为必选属性。
定义:
type Required<T> = {[P in keyof T]-?: T[P];
}
复制代码
使用:
interface Person {name?: stringage?: number
}// 使用 Required 映射后返回的新类型,name 和 age 都变成了必选属性
// 会报错:Type '{}' is missing the following properties from type 'Required<Person>': name, age
let person: Required<Person> = {}
复制代码
提取属性(Pick<T>
)
定义:
type Pick<T, K extends keyof T> = {[P in K]: T[P];
}
复制代码
从
T
类型中提取部分属性,作为新的返回类型。
使用:比如我们在发送网络请求时,只需要传递类型中的部分属性,就可以通过
Pick
来实现。
interface Goods {type: stringgoodsName: stringprice: number
}// 作为网络请求参数,只需要 goodsName 和 price 就可以
type RequestGoodsParams = Pick<Goods, 'goodsName' | 'price'>
// 返回类型:
// type RequestGoodsParams = {
// goodsName: string;
// price: number;
// }
const params: RequestGoodsParams = {goodsName: '',price: 10
}
复制代码
排除属性(Omit<T>
)
定义:
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
和
Pick
作用相反,用于从T
类型中,排除部分属性用法:比如长方体有长宽高,而正方体长宽高相等,所以只需要长就可以,那么此时就可以用
Omit
来生成正方体的类型
interface Rectangular {length: numberheight: numberwidth: number
}type Square = Omit<Rectangular, 'height' | 'width'>
// 返回类型:
// type Square = {
// length: number;
// }const temp: Square = { length: 5 }
复制代码
摘取类型(Extract<T, U>
)
语法:
Extract<T, U>
提取
T
中可以 赋值 给U
的类型定义:
type Extract<T, U> = T extends U ? T : never;
用法:
type T01 = Extract<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "a" | "c"type T02 = Extract<string | number | (() => void), Function>; // () => void
复制代码
排除类型(Exclude<T, U>
)
语法:
Exclude<T, U>
与
Extract
用法相反,从T
中剔除可以赋值给U
的类型定义:
type Exclude<T, U> = T extends U ? never : T
用法:
type T00 = Exclude<"a" | "b" | "c" | "d", "a" | "c" | "f">; // "b" | "d"type T01 = Exclude<string | number | (() => void), Function>; // string | number
复制代码
属性映射(Record<K, T>
)
定义:
type Record<K extends string | number | symbol, T> = {[P in K]: T;
}
复制代码
接收两个泛型,
K
必须可以是可以赋值给string | number | symbol
的类型,通过in
操作符对K
进行遍历,每一个属性的类型都必须是T
类型
用法:比如我们想要将
Person
类型的数组转化成对象映射,可以使用Record
来指定映射对象的类型
interface Person {name: stringage: number
}const personList = [{ name: 'Jack', age: 26 },{ name: 'Lucy', age: 22 },{ name: 'Rose', age: 18 },
]const personMap: Record<string, Person> = {}personList.map((person) => {personMap[person.name] = person
})
复制代码
比如在传递参数时,希望参数是一个对象,但是不确定具体的类型,就可以使用 Record
作为参数类型
function doSomething(obj: Record<string, any>) {
}
复制代码
不可为空类型(NonNullable<T>
)
定义:
type NonNullable<T> = T extends null | undefined ? never : T
从 T 中剔除
null
、undefined
、never
类型,不会剔除void
、unknow
类型
type T01 = NonNullable<string | number | undefined>; // string | numbertype T02 = NonNullable<(() => string) | string[] | null | undefined>; // (() => string) | string[]type T03 = NonNullable<{name?: string, age: number} | string[] | null | undefined>; // {name?: string, age: number} | string[]
复制代码
构造函数参数类型(ConstructorParameters<typeof T>
)
返回 class 中构造函数参数类型组成的 元组类型
定义:
/*** Obtain the parameters of a constructor function type in a tuple*/
type ConstructorParameters<T extends new (...args: any) => any> = T extends new (...args: infer P) => any ? P : never;
复制代码
使用:
class Person {name: stringage: numberweight: numbergender: 'man' | 'women'constructor(name: string, age: number, gender: 'man' | 'women') {this.name = namethis.age = age;this.gender = gender}
}type ConstructorType = ConstructorParameters<typeof Person> // [name: string, age: number, gender: "man" | "women"]const params: ConstructorType = ['Jack', 20, 'man']
复制代码
实例类型(InstanceType<T>
)
获取 class 构造函数的返回类型
定义:
/*** Obtain the return type of a constructor function type*/
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
复制代码
使用:
class Person {name: stringage: numberweight: numbergender: 'man' | 'women'constructor(name: string, age: number, gender: 'man' | 'women') {this.name = namethis.age = age;this.gender = gender}
}type Instance = InstanceType<typeof Person> // Personconst params: Instance = {name: 'Jack',age: 20,weight: 120,gender: 'man'
}
复制代码
函数参数类型(Parameters<T>
)
获取函数的参数类型组成的 元组
定义:
/*** 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;
复制代码
用法:
type FunctionType = (name: string, age: number) => booleantype FunctionParamsType = Parameters<FunctionType> // [name: string, age: number]const params: FunctionParamsType = ['Jack', 20]
复制代码
函数返回值类型(ReturnType<T>
)
获取函数的返回值类型
定义:
/*** Obtain the return type of a function type*/
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
复制代码
使用:
type FunctionType = (name: string, age: number) => boolean | stringtype FunctionReturnType = ReturnType<FunctionType> // boolean | string
复制代码
四、总结
高级类型
用法 描述 & 交叉类型,将多个类型合并为一个类型,交集 \ 联合类型,将多个类型组合成一个类型,可以是多个类型的任意一个,并集 关键字
用法 描述 T extends U 类型约束,判断 T 是否可以赋值给 U P in T 类型映射,遍历 T 的所有类型 parameterName is Type 类型谓词,判断函数参数 parameterName 是否是 Type 类型 infer P 待推断类型,使用 infer 标记类型 P,就可以使用待推断的类型 P typeof v === "typename" 原始类型保护,判断数据的类型是否是某个原始类型( number
、string
、boolean
、symbol
)instanceof v 类型保护,判断数据的类型是否是构造函数的 prototype
属性类型keyof 索引类型查询操作符,返回类型上已知的 公共属性名 T[K] 索引访问操作符,返回 T
对应属性 P 的类型映射类型
用法 描述 Readonly 将 T 中所有属性都变为只读 ReadonlyArray 返回一个 T 类型的只读数组 ReadonlyMap<T, U> 返回一个 T 和 U 类型组成的只读 Map Partial 将 T 中所有的属性都变成可选类型 Required 将 T 中所有的属性都变成必选类型 Pick<T, K extends keyof T> 从 T 中摘取部分属性 Omit<T, K extends keyof T> 从 T 中排除部分属性 Exclude<T, U> 从 T 中剔除可以赋值给 U 的类型 Extract<T, U> 提取 T 中可以赋值给 U 的类型 Record<K, T> 返回属性名为 K,属性值为 T 的类型 NonNullable 从 T 中剔除 null 和 undefined ConstructorParameters 获取 T 的构造函数参数类型组成的元组 InstanceType 获取 T 的实例类型 Parameters 获取函数参数类型组成的元组 ReturnType 获取函数返回值类型
写在后面
如果有写的不对或不严谨的地方,欢迎大家能提出宝贵的意见,十分感谢。
TypeScript 高级类型及用法相关推荐
- TypeScript高级类型:联合类型、交叉类型和类型别名
目录 引言 联合类型 交叉类型 类型别名 注意 结论 引言 TypeScript 是一门强类型语言,其高级类型功能使得开发者能够更加灵活地定义类型并对其进行操作,以便于更好地编写可维护和可扩展的代码. ...
- PART16 TypeScript高级类型
文章目录 TypeScript高级类型 class类 构造函数 实例方法 类的继承 类成员的可见性 类型兼容性 接口之间的兼容性 函数之间的兼容性 参数个数 参数类型 返回值类型 交叉类型 泛型 泛型 ...
- TypeScript高级类型-Partial、Required、Pick、Omit、Readonly
Partial (可选属性,但仍然不允许添加接口中没有的属性) ts中就是让一个定义中的所有属性都变成可选参数,参数可以变多也可以少. 我们定义 一个user 接口,如下 interface IUse ...
- 【前端进阶】-TypeScript高级类型 | 泛型约束、泛型接口、泛型工具类型
前言 博主主页
- 【前端进阶】-TypeScript高级类型 | 交叉类型、索引签名类型、映射类型
前言 博主主页
- 5.TypeScript入门之TS高级类型(class类)
上一章节:4.TypeScript入门之TS常用类型(3) Ⅳ.TypeScript高级类型 概述 TS中的高级类型有很多,重点学习以下高级类型: class类 类型兼容性 交叉类型 泛型和 keyo ...
- 类型全部为string_TypeScript 高级类型总结(含代码案例)
// 每日前端夜话 第458篇// 正文共:2600 字// 预计阅读时间:10 分钟 TypeScript 是一种类型化的语言,允许你指定变量.函数参数.返回的值和对象属性的类型. 以下是 Type ...
- boolean类型_10、typescript的高级类型
所谓高级类型,是typescript为了保证语言的灵活性,所使用的一下语言特性.这些特性有助于我们应对复杂多变的开发场景. 交叉类型 将多个类型合并成一个类型,新的类型将具有所有类型的特性,所以交叉类 ...
- TypeScript基础入门之高级类型的可辨识联合(Discriminated Unions)
2019独角兽企业重金招聘Python工程师标准>>> 转发 TypeScript基础入门之高级类型的可辨识联合(Discriminated Unions) 高级类型 可辨识联合(D ...
最新文章
- 新东家要哭了,雅虎终于承认上亿用户数据被盗
- 大话“用户注册激活,忘记密码”发送邮件功能
- NumPy学习_00 ndarray的创建
- .net 垃圾回收学习[How To: Use CLR Profiler][翻译学习]【2】
- [转载] 【Java】将一个字符串的字符排序,按ASCII表的顺序从小到大
- 一加Ace外观设计理念揭晓:主推硬朗直线条力量感/速度感十足
- python攻击局域网电脑_怎么攻击对方电脑?以知对方IP,且对方在线
- 一级计算机办公软件,计算机一级与高级办公软件
- 快商通知识图谱工程:让信息不再零碎,构建全行业的知识库 |百万人学AI评选
- android数据适配器参数,Android 万能适配器BRVAH
- python 实现 责任链模式
- Grunt 的简单使用
- 很有道理的程序员的小故事
- 实现页面的图文混排布局(Web作业)
- cpu要和gpu搭配吗_谈谈CPU和显卡如何搭配
- Carson带你学Android:源码解析自定义View Draw过程
- GitHub使用方法(初级)
- java 网上医院预约系统_基于java web的医院网上预约挂号系统 代码+数据库文件 齐全...
- 微信小程序(游戏)----拖拽拼图(图片分块和打乱顺序)
- 最强整理:Java开发究竟该如何学习,大厂面经合集