再学 TypeScript (2) 高级类型

前言

在以前已经从新的去了解了一下基础类型的一些知识,如今就继续向前,进一步了解下 TypeScript 中的高级类型以及一些用法typescript

1、字面量类型

  • 字符串字面量类型数组

    有时候能够直接定义字符串字面量类型简单方便的指定变量的固定值,实现相似枚举类型的字符串:安全

    type direction = 'up' | 'down' | 'left' | 'right' | 'static'
    
    function move(type: number): direction {
      switch(type) {
        case 0:
          return 'up'
        case 1:
          return 'down'
        case 2:
          return 'left'
        case 3:
          return 'right'
        default:
          return 'static'
      }
    }
    复制代码
  • 数字字面量类型dom

    TypeScript 还具备数字字面量类型:函数

    type nums = 0 | 1 | 2
    复制代码

2、索引类型

当咱们须要获取某个对象中的一些属性值时,一般会是这样:学习

const person = {
  name: 'tom',
  age: 11
}

function getProps(obj: any, keys: string[]) {
  return keys.map(key => obj[key])
}

getProps(person, ['name', 'age']) // ['tom', 11]

// 当指定的 key 不是对象的 key 时,编译器并无提示错误
getProps(person, ['sex'])  // [undefined] 
复制代码

这时候,可使用索引类型,编译器就可以检查使用了动态属性名的代码:ui

interface Person {
  name: string;
  age: number;
}

function getProps<T, K extends keyof T>(person: T, keys: K[]): T[K][] {
  return keys.map(key => person[key])
}

const person = {
  name: 'tom',
  age: 11
}

getProps(person, ['name'])  // ['tom']
getProps(person, ['sex'])  // 报错 不能将类型“"sex"”分配给类型“"name" | "age"”
复制代码

能够发现,在定义方法 getProps 时,使用了几个操做符,理解下这些操做符的做用this

  • 索引类型查询操做符 keyof Tspa

    对于任何类型 Tkeyof T 的结果为 T 上已知的公共属性名的联合类型。例如:prototype

    let personProps: keyof Person // 'name' | 'age'
    // 也就是等同于下面定义
    let personProps: 'name' | 'age'
    复制代码
  • 索引访问操做符 T[K]

    在这里,类型语法反映了表达式语法。 这意味着 person['name'] 具备类型 Person['name']。然而,就像索引类型查询同样,你能够在普通的上下文里使用 T[K] ,这正是它的强大所在。 你只要确保类型变量 K extends keyof T 就能够了。 例以下面 getProperty 函数的例子:

    function getProperty<T, K extends keyof T>(o: T, name: K): T[K] {
      return o[name]
    }
    
    // 当你返回 T[K]的结果,编译器会实例化键的真实类型
    // 所以 getProperty 的返回值类型会随着你须要的属性改变
    
    const name = getProperty(person, 'name')  // string 类型
    const age = getProperty(person, 'age')  // number 类型
    复制代码
  • 继承 extends

    K extends keyof T 是泛型约束,泛型变量经过继承某些类型获取某些属性,表示泛型 K 继承 keyof T 的属性名

索引类型和字符串索引签名

keyofT[K] 与字符串索引签名进行交互。 若是你有一个带有字符串索引签名的类型,那么 keyof T 会是 string 。 而且 T[string] 为索引签名的类型:

interface Obj<T> {
  [key: string]: T
}

let keys: keyof Obj<number> // string 类型
let value: Obj<number>['foo'] // number 类型

// 能够利用索引类型定义属性值为某种类型的对象
const datesObj: Obj<number> = {  
  yesterday: 8,
  today: 9,
  tomorrow: 10
}

// 一样 数组也是适用的
interface Arr<T> {
  [index: number]: T
}
const datesArr: Arr<number> = [8, 9, 10]
复制代码

3、映射类型

映射类型是 TypeScript 提供了从旧类型中建立新类型的一种方式,在映射类型里,新类型以相同的形式去转换旧类型里每一个属性。

当须要某个类型可是对属性要求不同时,就可使用映射类型来指定:

interface Person {
  name: string
  age: number
}

type partial<T> = {
  [P in keyof T]?: T[P]  // 建立一个映射类型 依赖 Person 类型 但属性是可选的
}

const Jack: partial<Person> = {
  name: 'jack'
}
复制代码

观察上面示例代码,在定义类型的 key 时,用到了前面的索引类型查询操做符(keyof,用来遍历每一个属性索引,经过 in 操做符指向 P ,能够理解成 for ... in

type Keys = 'opt1' | 'opt2'
type Flags = {
  [K in Keys]: boolean
}
// 类型变量 K,它会依次绑定到每一个属性
// 字符串字面量联合的 Keys,它包含了要迭代的属性名的集合
// 属性的结果类型 Flags
// 这个映射类型等同于:
type Flags = {
  opt1: boolean
  opt2: boolean
}
复制代码

4、交叉类型与联合类型

  • 交叉类型:

    交叉类型是将多个类型合并为一个类型。 这让咱们能够把现有的多种类型叠加到一块儿成为一种类型,它包含了所需的全部类型的特性。

  • 联合类型:

    联合类型表示一个值能够是几种类型之一。

从这两种类型的定义中,能够知道,交叉类型( A & B)同时具备类型 A 和类型 B 二者的全部属性,能够访问全部子类型的成员;联合类型(X | Y)则是表示值是其中一种类型,只能访问子类型的共有的成员。

interface A {
  name: string
  age: number
  getName: () => string
}

interface B {
  name: string
  age: number
  getAge: () => number
}

function getIntersection (): A & B { }
function getUnion(): A | B { }

// 交叉类型 具备全部子类型的属性
const x = getVariable()
x.getName()
x.getAge()

// 联合类型 表示值的类型是几种类型之一
// 能肯定的就是共有的属性,因此能够直接访问
// 不能访问某个子类型单独具备的属性
const y = getUnion()
y.getName()  // 不存在该属性
y.getAge()  // 不存在该属性
y.name  // 能够访问
复制代码

在某些状况下,当咱们肯定一个联合类型的变量是其中哪一种类型或者须要在某种类型时会执行类型下的一些方法,若是不是公共成员会报错,这时候就须要用上类型断言了:

(<B>y).getName()
复制代码

在实际开发中,交叉类型用的比较少,更多状况下,用的是联合类型。

5、类型注解、类型推断和类型断言以及类型保护

类型注解

所谓类型注解,就是人为为一个变量指定类型,例如:

const count: number = 0
复制代码

在定义变量的时候能够手动给变量添加类型,不过其实是不须要的,由于 TypeScript 会根据变量值自动推断出变量的类型,若是没法推断就默认为any类型,这就是类型推断。所以,在大部分状况下,咱们不须要去写类型注解;可是在某些没法推断的状况下就须要类型注解了,例如函数的参数:

// 参数的类型没法推断 默认其类型为 any
// 因此 x 、y 和 sum 都被推断成了 any 类型
function add(x, y) {
  return x + y
}
const sum = add(1, 1)

// 这时就用上类型注解了
// 指定参数的类型是 number ts 会推断出 sum 也是 number 类型
function add(x: number, y: number) {
  return x + y
}
const sum = add(1, 1)
复制代码

还有一种状况就是变量声明时未赋值,也没法推断:

let z;  // any 类型
z = 9
复制代码

类型断言

当咱们肯定某个变量的类型,比定义时推断或注解的更准确,能够经过类型断言来手动指定变量的类型。它只是编译阶段起做用,检查代码,并不会实际的进行类型的转换。

const str: any = 'something'
const len1 = <string>str.length  // 尖括号语法
const len2 = (str as string).length  // as 语法
复制代码

在一些没法肯定其具体类型的状况下,在函数实现中,一般须要区分出具体类型:

interface Bird {
  fly()
  layEggs()
}
interface Fish {
  swim()
  layEggs()
}
function getSmallPet(): Fish | Bird {
    // ...
}
let pet = getSmallPet()

pet.fly()  // 错误 类型“Bird | Fish”上不存在属性“fly”
pet.swin()  // 错误 类型“Bird | Fish”上不存在属性“swin”

// 使用类型断言
if ((<Fish>pet).swim) {
  (<Fish>pet).swim()
} else {
  (<Bird>pet).fly()
}
复制代码

类型保护

上面例子中,为了经过类型检查,须要屡次使用类型断言,明显这不是一种优雅的办法,最好的办法就是一旦检查过类型就记录下来,不须要屡次的指定类型。这种就是类型保护。类型保护就是一些表达式,它们会在运行时检查以确保在某个做用域里的类型。

  • 自定义类型保护

    要定义一个类型保护,咱们只要简单地定义一个函数,它的返回值是一个 类型谓词

    function isFish(pet: Fish | Bird): pet is Fish {
      return (<Fish>pet).swim !== undefined
    }
    复制代码

    pet is Fish 就是类型谓词,谓词为 parameterName is Type 这种形式, parameterName 必须是来自于当前函数的一个参数名。每当使用一些变量调用 isFish 时,TypeScript 会将变量缩减为那个具体的类型,只要这个类型与变量的原始类型是兼容的。

    if (isFish(pet)) {
      pet.swim()
    } else {
      pet.fly()
    }
    复制代码
  • typeof 类型保护

    在 JavaScript 中,一般使用 typeof 来肯定基本类型, TypeScript 能识别 typeof 做为类型保护,能够直接在代码里检查类型了。

    function getAttributeValue(attribute: number | string) {
      if(typeof attribute === 'string') {
        // ...
      } else {
        // ...
      }
    }
    复制代码

    typeof 类型保护只有两种形式能被识别: typeof v === "typename"typeof v !== "typename"

    "typename"必须是 "number""string""boolean""symbol"

  • instanceof 类型保护

    instanceof 运算符用于检测构造函数的 prototype 属性是否出如今某个实例对象的原型链上。

    相似于typeof 检测基本类型,instanceof 用来检测实例与类的所属关系,也是一种类型保护,是经过构造函数来细化类型的一种方式。

    class NumberValue {
      constructor(private value: number) { }
      public getValue() {
        return (this.value * 100).toString()
      }
    }
    class StringValue {
      constructor(private value: string) { }
      public getValue() {
        return (Number(this.value) * 100).toString()
      }
    }
    function getValue() {
      return Math.random() < 0.5 ? new NumberValue(1) : new StringValue('2')
    }
    
    const value = getValue()
    if(value instanceof NumberValue) {
      // ...
    } else {
      // ...
    }
    复制代码
  • in 类型保护

    若是指定的属性在指定的对象或其原型链中,则 in 运算符返回true

    in 操做符能够安全的检查一个对象上是否存在一个属性,它一般也被作为类型保护使用:

    interface X {
    	x: number
    }
    interface Y {
    	y: number
    }
    
    function do(arg: X | Y) {
      if('x' in X) {
        // ...
      } else {
        // ...
      }
    }
    复制代码
  • 字面量类型保护

    在联合类型里使用字面量类型时,能够直接经过判断联合类型中的公共属性:

    type X {
    	name: 'x'
    	do: number
    }
    type Y {
    	name: 'y'
    	do: number
    }
    
    function do(arg: X | Y) {
      if(arg.name === 'x') {
        // ...
      } else {
        // ...
      }
    }
    复制代码

6、类型别名 type

TypeScript 使用 type 关键字声明类型别名,类型别名并不会新建一个类型,只是建立一个名字来引用一些类型。

type Name = string
type Container<T> = { value: T }  // 类型别名也能够是泛型
type Tree<T> = {  // 可使用类型别名来在属性里引用本身
    value: T
    left: Tree<T>
    right: Tree<T>
}
复制代码

typeinterface

  • 这二者都是类型约束的主要形式,能够限定对象的类型,检查类型。通常在一些简单的类型定义,能够同样使用

  • type 并无 interface 那么多的使用场景, interface 能够被被 extendsimplements,可是 type 不行。

type TPersion = {
  name: string
  age: number
}

interface IPersion {
  name: string
  age: number
}

const jack: TPersion = {
  name: 'jack',
  age: 20
}

const tom: IPersion = {
  name: 'tom',
  age: 21
}	

interface IMan extends IPersion {
  log: () => void
}

class Man implements IMan {
  name: string
  age: number
  log() {
    console.log(this.name)
  }
}
复制代码

至此,已经简单的梳理了一些高级类型的知识点,这篇其实早就快写完了,前段时间由于工做太忙,而后又本身犯懒鸽了很久,也是一直没搞学习,一眨眼就一个月多过去了。如今空闲点了,再从新搞起。

相关文章
相关标签/搜索