ts入门了一段时间,但是碰上一些高级类型老是一头雾水,还须要去看文档,因此总结了一些工做中常常用到的一些高级类型。因此阅读以前你须要掌握一点儿ts基础。typescript
交叉类型将多个类型合并为一个新的类型,新的具备全部参与合并的类型的特性,本质上是一种并的操做。形式以下:安全
T & U
//例子:
function extend<T , U>(first: T, second: U) : T & U {
let result: <T & U> = {}
for (let key in first) {
result[key] = first[key]
}
for (let key in second) {
if(!result.hasOwnProperty(key)) {
result[key] = second[key]
}
}
return result
}
复制代码
联合类型用 | 分割每个类型,表示只能取其中的一种类型,好比: number | string | boolean 的类型只能是这三个的一种,不能共存。 若是一个值的类型是联合类型,那么咱们只能访问它们中共有的属性或者方法,本质上是一种交的关系,好比:bash
interface Cat {
eat(food) : void
miao() : string
}
interface Dog {
eat(food) : void
wang() : string
}
function getPet() : Cat | Dog {
...
}
let pet = getPet()
pet.eat() //正确
pet.miao() //报错
复制代码
联合类型让一个值能够为不一样的类型,但随之带来的问题就是访问非共同方法时会报错。那么该如何区分值的具体类型,以及如何访问共有成员?函数
let pet = getPet()
if((<Cat>pet).miao) {
(<Cat>pet).miao()
} else {
(<Dog>pet).wang()
}
复制代码
可见使用类型断言就不得不写一堆蹩脚的尖括号,那么尚未更简洁明了办法来判断类型呢?ui
function isCat(pet: Cat | Dog) : pet is Cat {
return (<Cat>pet).miao !== undefined
}
复制代码
这种param is SomeType 的形式就是类型保护,咱们能够用它来明确一个联合类型变量的具体形式,在调用时ts就会将变量缩减为该具体类型,调用就是合法的了this
if (isCat(pet)) {
pet.miao() //正确
} else {
pet.wang()
}
复制代码
容许这么作是由于:typescript可以经过类型保护知道if语句里的pet类型必定是Fish类型,并且else语句里的pet类型必定不是Fish类型,那么就是Bird类型了spa
当咱们使用了typeof和instanceof后,typescript就会自动限制类型为某一具体类型,从而咱们能够安全地在语句体内使用具体类型的方法和属性,如:prototype
function show(param: number | string) {
if (typeof param === 'number') {
console.log(`${param} is number`)
} else {
console.log(`${param} is string`)
}
}
复制代码
可是typeof只支持number、string、boolean或者symbol(只有这些状况下能够被认为是类型保护)code
若是是类,咱们能够用instanceof对象
let an = getSomeKindType()
if (an instanceof Animal) {
an // 此时aa会细化为Animal类型
}
if (an instanceof People) {
an //此时aa会细化为People类型
}
复制代码
ts要求instanceof右侧是一个构造函数,而ts会将其细化为:
null和undefined能够赋给任何的类型,由于它们是全部其余类型的一个有效值,如:
// 如下语句均合法
let x1: number = null
let x2: string = null
let x3: boolean = null
let x4: undefined = null
let y1: number = undefined
let y2: string = undefined
let y3: boolean = undefined
let y4: null = undefined
复制代码
在typescript里,咱们可使用--strictNullChecks标记,开启这个标记后,当咱们声明一个变量时,就不会自动包含null或undefined,可是咱们能够手动使用联合类型来明确包含,如:
let x = 123
x = null // 报错
let y: number | null = 123
y = null // 容许
y = undefined // 报错,`undefined`不能赋值给`number | null`
复制代码
当开启了--strictNullChecks,可选参数/属性就会被自动地加上| undefined,如:
function foo(x: number, y?: number) {
return x + (y || 0)
}
foo(1, 2) // 容许
foo(1) // 容许
foo(1, undefined) // 容许
foo(1, null) // 报错,不容许将null赋值给`number | undefined`类型
复制代码
类型别名能够给现有的类型起个新名字,它和接口很像但又不同,由于类型别名能够做用于原始值、联合类型、元组及其余任何须要手写的类型,语法如:
type Name = string
别名不会新建一个类型,它只会建立一个新的名字来引用现有类型。因此在VSCode里将鼠标放在别名上时,显示的是所引用的那个类型
type Container<T> = {
value: T
}
let name: Container<string> = {
value: 'hello'
}
复制代码
字符串字面量类型容许咱们定义一个别名,类型为别名的变量只能取固定的几个值,如:
type Easing = 'ease-in' | 'ease-out' | 'ease-in-out'
let x1: Easing = 'uneasy' // 报错: Type '"uneasy"' is not assignable to type 'Easing'
let x2: Easing = 'ease-in' // 容许
复制代码
能够合并字符串字面量类型、联合类型、类型保护和类型别名来建立可辨识联合的高级模式(也称为标签联合或者代数数据类型),具备3个要素:
建立一个可辨识联合类型,首先须要声明将要联合的接口,每一个接口都要有一个可辨识的特征,如(kind属性):
interface Square {
kind: 'square'
size: number
}
interface Rectangle {
kind: 'rectangle'
width: number
height: number
}
interface Circle {
kind: 'circle'
radius: number
}
复制代码
如今,各个接口之间仍是没有关联的,因此咱们须要使用类型别名来联合这几个接口,如:
type Shape = Square | Rectangle | Circle
复制代码
如今,使用可辨识联合,如:
function area(s: Shape) {
switch (s.kind) {
case 'square':
return s.size * s.size
case 'rectangle':
return s.height * s.width
case 'circle':
return Math.PI * s.radius ** 2
}
}
复制代码
多态的this类型表示的是某个包含类或接口的子类型,例子如:
class BasicCalculator {
public constructor(protected value: number = 0) {
}
public currentValue(): number {
return this.value
}
public add(operand: number): this {
this.value += operand
return this
}
public multiply(operand: number): this {
this.value *= operand
return this
}
}
let v = new BasicCalculator(2).multiply(5).add(1).currentValue() // 11
复制代码
因为使用了this类型,当子类继承父类的时候,新的类就能够直接使用以前的方法,而不须要作任何的改变,如:
class ScientificCalculator extends BasicCalculator {
public cconstructor(value = 0) {
super(value)
}
public sin() {
this.value = Math.sin(this.value)
return this
}
}
let v = new BasicCalculator(2).multiply(5).sin().add(1).currentValue()
复制代码
若是没有this类型,那么ScientificCalculator就不可以在继承BasicCalculator的同时还保持接口的连贯性。由于multiply方法会返回BasicCalculator类型,而BasicCalculator没有sin方法。然而,使用this类型,multiply就会返回this,在这里就是ScientificCalculator
索引类型能使编译器可以检查使用了动态属性名的代码,好比咱们想要写一个函数,它能够选取对象中的部分元素的值,那么:
function pluck<T, K entends keyof T>(o: T, names: K[]) : T[K][] {
return names.map(n => o[n])
}
interface Person {
name: string
age: number
}
let p: Person = {
name: 'LL',
age: 18
}
let res = pluck(p, ['name']) //容许
复制代码
以上代码解释以下:
因此,根据以上例子,触类旁通有:
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key]
}
let obj = {
name: 'LL',
age: 18,
male: true
}
let x1 = getProperty(obj, 'name') // 容许,x1的类型为string
let x2 = getProperty(obj, 'age') // 容许,x2的类型为number
let x3 = getProperty(obj, 'male') // 容许,x3的类型为boolean
let x4 = getProperty(obj, 'hobby') // 报错:Argument of type '"hobby"' is not assignable to parameter of type '"name" | "age" | "male"'.
复制代码
keyof和T[K]与字符串索引签名进行交互,若是有一个带有字符串索引签名的类型,那么keyof T为string,且T[string]为索引签名的类型,如:
interface Demo<T> {
[key: string]: T
}
let keys: keyof Demo<boolean> // keys的类型为string
let value: Demo<number>['foo'] // value的类型为number
复制代码
咱们可能会遇到这么一些需求: 1)将一个现有类型的每一个属性都变为可选的,如:
interface Person {
name: string
age: number
}
复制代码
可选版本为:
interface PersonPartial {
name?: string
age?: number
}
复制代码
2)或者将每一个属性都变为只读的,如:
interface PersonReadonly {
readonly name: string
readonly age: number
}
复制代码
而如今typescript为咱们提供了映射类型,可以使得这种转化更加方便,在映射类型里,新类型将以相同的形式去转换旧类型里每一个属性,如以上例子能够改写为:
type Readonly<T> = {
readonly [P in keyof T]: T[P]
}//ts 源码
type Partial<T> = {
[P in keyof T]?: T[P]
}//ts源码
type PersonReadonly = Readonly<Person>
type PersonPartial = Partial<Person>
复制代码
另外ts还给咱们提供了Required,Pick,Record,几种经常使用到的类型,下面是它们的源码:
type Required<T> = {
[P in keyof T]-?: T[P];
}
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
}
type Record<K extends keyof any, T> = {
[P in K]: T;
}
type personRequired = Required<Person>;
// personRequired === {name: string; age: number}
type personPick = Pick<Person, "name">;
// person5 === {name: string}
type personRecord = Record<'name' | 'age', string>
// personRecord === {name: string; age: string}
复制代码
咱们还能够写出更多的通用映射类型,如:
// 可为空类型
type Nullable<T> {
[P in keyof T]: T[P] | null
}
// 包装一个类型的属性
type Proxy<T> = {
get(): T
set(value: T): void
}
type Proxify<T> = {
[P in keyof T]: Proxy<T[P]>
}
function proxify(o: T): Proxify<T> {
// ...
}
let proxyProps = proxify(props)
复制代码
上面展现了如何包装一个类型,那么与之相反的就有拆包操做,示例如:
function unproxify<T>(t: Proxify<T>): T {
let result = <T>{}
for (const k in t) {
result[k] = t[k].get()
}
return result
}
let originalProps = unproxify(proxyProps)
复制代码