一、联合类型和交叉类型
1. 联合类型
- TypeScript 的类型系统允许我们使用多种运算符,从现有类型中构建新类型
- 联合类型(Union Type)
- 联合类型是由两个或多个其它类型组成的类型
- 表示可以是这些类型中的任何一个值
- 联合类型中每一个类型被称为联合成员(union' members)
2. 联合类型的使用
- 传入给一个联合类型的值是非常简单的:只要保证是联合类型中的某一个类型的值即可
- 拿到这个值后,如何使用它?因为它可能是任何一种类型
- 如我们拿到的值可能是 string 或 number,就不能对其调用string上的一些方法
- 该如何处理这样的问题?
- 需要缩小(narrow)联合类型
- TypeScript 可以根据我们缩小的代码结构,推断出更加具体的类型
二、type和interface使用
1. 类型别名 type
- 通过在类型注解中编写对象类型和联合类型,当我们要多次在其它地方使用时,就要编写多次,很不方便
- 此时,可以给对象类型起一个别名复用
type Point = {
x: number
y: number
z?: number
}
2. 接口的声明
- 前面讲了通过 type 来声明一个对象类型
- 对象的另外一种声明方式就是通过接口来声明
interface Point {
x: number
y: number
}
- 二者的区别
- 类型别名和接口非常相似,在定义对象类型时,可以任意选择使用
- 接口的几乎所有特性都可以在 type 中使用
3. interface 和 type 区别
- interface 和 type 都可以用来定义对象类型
- 如果定义非对象类型,通常推荐使用 type,比如Direction、Alignment、一些Function
- 如果定义的是对象类型,区别如下
- interface 可以重复的对某个接口来定义属性和方法
- type 定义的是别名,别名是不能重复的
- 所以,interface 可以为现有的接口提供更多的扩展
4. 交叉类型
- 联合类型表示多个类型中的一个即可
- 交叉类型(Intersection Types)
- 交叉类型表示需要满足多个类型的条件
- 交叉类型使用 & 符号
- type MyType = number & string
- 表达式含义是 number 和 string 要同时满足
- 但没有同时满足 number、string 的值,所以 MyType 其实就是一个 never类型
- 交叉类型的应用
interface Colorful {
color: string
}
interface IKun {
running: () => void
}
type NewType = Colorful & IKun
const obj: NewType = {
color: 'red',
running: function() {}
}
三、类型断言和非空断言
1. 类型断言 as
- 有时候 TypeScript 无法获取具体的类型信息,此时需要使用类型断言(Type Assertions)
- 比如通过 document.getElementById,TypeScript 只知道该函数会返回 HTMLElement,并不知道具体的类型
const img = document.getElementById('.img') as HTMLImageElement
img.src = 'xxx'
- TypeScript 只允许类型断言转换为
更具体或不太具体(any、unknown)的类型版本,此规则可防止不可能的强制转换
2. 非空类型断言!
- 如下代码在执行ts的编译阶段会报错
- 这是因为传入的 msg 有可能是 undefined,这时是不能执行方法的
function getMsg(msg?: string) {
console.log(mesg.toUpperCase())
}
getMsg("haha")
- 但,我们确定传入的参数是有值的,这个时候可以使用非空类型断言
- 非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过 ts 在编译阶段对它的检测
function getMsg(msg?: string) {
console.log(mesg!.toUpperCase())
}
四、字面量类型和类型缩小
1. 字面量类型
let msg: "Hello Vue" = "Hello Vue"
type Alignment = 'left' | 'right' | 'center'
2. 字面量推理
const info = {
url: 'xxx',
method: 'GET'
}
function request({url: string, method: 'GET' | 'POST'}) {
console.log(url, method)
}
request(info.url, info.method)
- 这是因为我们的对象在进行字面量推理的时候,info其实是一个 {url: string, method: string},所以没有办法将一个 string赋值给一个字面量类型
3. 类型缩小
- 什么是类型缩小?
- Type Narrowing(又称类型收窄)
- 可以通过类似 typeof padding === 'number' 的判断语句,来改变 TypeScript 的执行路径
- 在给定的执行路径中,可以缩小比声明时更小的类型,这个过程称之为 缩小(Narrowing)
- 编写的 typeof padding === 'number' 称为 类型保护(type guards)
- 常见的类型保护有如下几种
- typeof
- 在 TypeScript 中,检查返回的值 typeof 是一种类型保护
- 因为 TypeScript 对如何 typeof 操作不同的值进行编码
- 平等缩小(如:===、!==)
- 可以使用 switch 或 相等 的一些运算符来表达相等性(如:===、!==、==、!=)
- instanceof
- JavaScript 有一个运算符来检查一个值是否是另一个值的 实例
- in
- JavaScript 有一个运算符,用于确定对象是否具有带名称的属性:in 运算符
- 如果指定的属性在指定的对象或其原型链中,则 in 运算符 返回 true
- 等等
五、函数的类型和函数签名
1. TypeScript 函数类型
- 在 JavaScript 开发中,函数是重要的组成部分,函数可以作为一等公民(可以作参数,可以作返回值进行传递)
- 在使用函数的过程中,函数也有自己的类型
- 函数类型的表达式(Function Type Expressions),来表示函数类型
type Func = (num1: number, num2: number) => void
2. TypeScript 函数类型解析
- (num1: number, num2: number) => void,代表的就是一个函数类型
- 接受两个参数的函数,并且都是 number 类型
- 函数没有返回值,是 void
- 在某些语言中,可能参数名 num1 和 num2 是可以省略的,但是 TypeScript 中不可以省略
3. 调用签名(Call Signatures)
- 在 JavaScript 中,函数除了可以被调用,自己也是可以有属性值的
- 然而上面写的函数类型表达式并不支持声明属性
- 如果想要描述一个带有属性的函数,可以在一个对象类型中写一个调用签名(Call signature)
interface Func {
name: string
(num1: number, num2: number): void
}
function calc(calcFn: Func) {
calcFn(1, 2)
}
- 注意:这个语法跟函数类型表达式稍有不同,在参数列表和返回的类型之间用的是 冒号: ,而不是 =>
4. 构造签名(Construct Signatures)
- JavaScript 函数也可以使用 new 操作符调用,当被调用时,TypeScript 会认为这是一个构造函数(constructors),会产生一个新对象
- 写一个构造签名(Construct Signatures),方法是在调用签名签名加上一个 new 关键词
interface IPerson {
new (name: string): Person
}
function func(ctor: IPerson) {
return new ctor("haha")
}
class Person {
name: string
constructor(name: string) {
this.name = name
}
}
func(Person)
5. 参数的可选类型
function func(x: number, y?:number) {
console.log(x, y)
}
6. 默认参数
- 从 ES6 开始,JavaScript 是支持默认参数的,TypeScript 也是支持默认参数的
function func(x: number, y: number = 10) {
console.log(x, y)
}
func(1)
- 此时 y 的类型其实是 undefined 和 number 类型的联合
7. 剩余参数
- 从 ES6 开始,JavaScript也支持剩余参数,剩余参数语法允许我们将一个不定数量的参数放到一个数组中
function sum(...nums: number[]) {
let total = 0
for (const num of nums) {
total += num
}
return total
}
const res = sum(1,2,3,4,5)
console.log(res)
六、函数的重载和this类型
1. 函数的重载(了解)
- 在 TypeScript 中,编写一个 add 函数,希望可以对字符串和数字类型进行相加
- 编写
- TypeScript 中,可以去编写不同的重载签名(overload signatures)来表示函数可以以不同的方式进行调用
- 一般是编写两个或以上的重载签名,再去编写一个通用的函数以及实现
2. sum函数的重载
- 对 sum 函数进行重构
- 在调用 sum 的时候,它会根据传入的参数类型决定执行函数体时,到底执行哪一个函数的重载签名
function sum(num1: number, num2: number): number;
function sum(num1: string, num2: string): string;
function sum(num1: any, num2: any): any {
return num1 + num2
}
console.log(sum(1, 2))
console.log(sum('a', 'b'))
3. 联合类型和重载
- 需求:定义一个函数,可以传入字符串或数组,获取长度
- 两种实现方案
function getLength(a: string | any[]) {
return a.length
}
function getLength(a: string): number
function getLength(a: any[]): number
function getLength(a: any) {
return a.length
}
4. 可推导的 this 类型
- 目前在 Vue3 和 React 开发中不一定使用到this
- Vue3 Composition API中很少见到 this
- React 的 Hooks 开发中也很少见到 this
- 如下代码在默认情况下是可以正常运行的,在 TypeScript 编译时,认为我们的 this 是可以正确去使用的
- 在没有指定this的情况下,this默认情况下是any类型的
5. this 的编译选项
- VSCode 在检测 TypeScript 代码时,默认情况下运行不确定的 this 按照 any 类型去使用
- 可以创建一个 tsconfig.json 文件,并在其中告知 VSCode 必须明确执行(不能是隐式的)
- tsconfig.json => noImplicitThis: true
- 在设置了 noImplicitThis 为 true 时,TypeScript 会根据上下文推导this,但是在不能正确推导时,就会报错的指定this
6. 指定 this 的类型
- 在开启 noImplicitThis 的情况下,必须制定 this 的类型
- 使用 函数的第一个参数类型来指定
- 函数的第一个参数可以根据该函数之后被调用的情况,用于声明this的类型(名字必须使用 this)
- 在后续调用函数传入参数时,从第二个参数开始传递的,this参数会在编译后被抹除
function foo(this: { name: string }) {
console.log(this)
}
foo.call({ name: 'ikun' })
7. this 相关的内置工具
- TypeScript 提供了一些工具类型来辅助进行常见的类型转换,这些类型全局可用
- ThisParameterType
- 用于提取一个函数类型 Type 的this参数类型
- 如果这个函数类型没有this参数,返回unknown
function foo(this: { name: string }) {
console.log(this.name)
}
type ThisType = ThisParameterType<typeof foo>
- OmitThisParameter
- 用于移除一个函数类型 Type 的this参数类型,并且返回当前的函数类型
type FnType = OmitThisParameter<typeof foo>
- ThisType
- 这个类型不返回一个转换过的类型,他被用作标记一个上下文的this类型
interface IState {
name: string
age: number
}
interface IStore {
state: IState
eating: () => void
running: () => void
}
const store: IStore & ThisType<IState> = {
state: {
name: 'ikun',
age: 17
},
eating: function() {
console.log(this.name)
},
runing: function() {
console.log(this.name)
}
}
store.eating.call(store.state)