TS 高级类型

681 阅读4分钟

「这是我参与2022首次更文挑战的第21天,活动详情查看:2022首次更文挑战」。

联合类型

如果希望一个变量可以支持多种类型,就可以用联合类型(union types)来定义。

例如,一个变量既支持 number 类型,又支持 string 类型,就可以这么写:

let num: number | string

num = 8
num = 'eight'

联合类型大大提高了类型的可扩展性,但当 TS 不确定一个联合类型的变量到底是哪个类型的时候,只能访问他们共有的属性和方法。

比如这里就只能访问 number 类型和 string 类型共有的方法,如下图,

image.png

如果直接访问 length 属性,string 类型上有,number 类型上没有,就报错了,

image.png

类型保护

如果有一个 getLength 函数,入参是联合类型 number | string,返回入参的 length,

function getLength(arg: number | string): number {
    return arg.length
}

从上文可知,这么写会报错,因为 number 类型上没有 length 属性。

image.png

这个时候,类型保护(Type Guards)出现了,可以使用 typeof 关键字判断变量的类型。

我们把 getLength 方法改造一下,就可以精准地获取到 string 类型的 length 属性了,

function getLength(arg: number | string): number {
    if(typeof arg === 'string') {
        return arg.length
    } else {
        return arg.toString().length
    }
}

之所以叫类型保护,就是为了能够在不同的分支条件中缩小范围,这样我们代码出错的几率就大大降低了。

类型断言

上文的例子也可以使用类型断言来解决。

类型断言语法:

as 类型

使用类型断言来告诉 TS,我(开发者)比你(编译器)更清楚这个参数是什么类型,你就别给我报错了,

function getLength(arg: number | string): number {
    const str = arg as string
    if (str.length) {
        return str.length
    } else {
        const number = arg as number
        return number.toString().length
    }
}

注意,类型断言不是类型转换,把一个类型断言成联合类型中不存在的类型会报错。

比如,

function getLength(arg: number | string): number {
    return (arg as number[]).length
}

image.png

交叉类型

如果要对对象形状进行扩展,可以使用交叉类型 &

比如 Person 有 name 和 age 的属性,而 Student 在 name 和 age 的基础上还有 grade 属性,就可以这么写,

interface Person {
    name: string
    age: number
}

type Student = Person & { grade: number }

这和类的继承是一模一样的,这样 Student 就继承了 Person 上的属性,

image.png

联合类型 | 是指可以取几种类型中的任意一种,而交叉类型 & 是指把几种类型合并起来。

交叉类型和 interface 的 extends 非常类似,都是为了实现对象形状的组合和扩展。

类型别名

类型别名(type aliase),听名字就很好理解,就是给类型起个别名。

就像 NBA 球员 扬尼斯-阿德托昆博,名字太长难记,我们叫他字母哥

就像我们项目中配置 alias,不用写相对路径就能很方便地引入文件

import componentA from '../../../../components/componentA/index.vue'
变成
import componentA from '@/components/componentA/index.vue

类型别名用 type 关键字来书写,有了类型别名,我们书写 TS 的时候可以更加方便简洁。

比如下面这个例子,getName 这个函数接收的参数可能是字符串,可能是函数,就可以这么写。

type Name = string
type NameResolver = () => string
type NameOrResolver = Name | NameResolver          // 联合类型
function getName(n: NameOrResolver): Name {
    if (typeof n === 'string') {
        return n
    }
    else {
        return n()
    }
}

这样调用时传字符串和函数都可以。

getName('lin')
getName(() => 'lin')

如果传的格式有问题,就会提示。

image.png

image.png

类型别名会给一个类型起个新名字。 类型别名有时和接口很像,但是可以作用于原始值,联合类型,元组以及其它任何你需要手写的类型。 -- TS 文档

类型别名的用法如下,

type Name = string                              // 基本类型

type arrItem = number | string                  // 联合类型

const arr: arrItem[] = [1,'2', 3]

type Person = { 
  name: Name 
}

type Student = Person & { grade: number  }       // 交叉类型

type Teacher = Person & { major: string  } 

type StudentAndTeacherList = [Student, Teacher]  // 元组类型

const list:StudentAndTeacherList = [
  { name: 'lin', grade: 100 }, 
  { name: 'liu', major: 'Chinese' }
]

字面量类型

有时候,我们需要定义一些常量,就需要用到字面量类型,比如,

type ButtonSize = 'mini' | 'small' | 'normal' | 'large'

type Sex = '男' | '女'

这样就只能从这些定义的常量中取值,乱取值会报错,

image.png

往期

轻松拿下 TS 泛型

TS 中 interface 和 type 究竟有什么区别?

TS 类型推论

通俗易懂的 TS 基础知识总结