非空类型断言
我们确定传入的参数是有值的,这个时候我们可以使用非空类型断言
非空断言使用的是 ! ,表示可以确定某个标识符是有值的,跳过ts在编译阶段对它的检测
function getMessage(message?: string) {
// 这种情况下message可能为空或者undefined,所以需要对message值为空的情况进行处理
if(message) {
// 类型缩小,所以此时的message的类型就是string
console.log(message.toUpperCase())
}
}
function getMessage(message?: string) {
// 非空断言
console.log(message!.toUpperCase())
}
可选链
- 可选链事实上并不是TypeScript独有的特性,它是ES11(ES2020)中增加的特性
- 可选链使用可选链操作符 ?.
- 它的作用是当对象的属性不存在时,会短路,直接返回undefined,如果存在,那么才会继续执行
- 可选链只能用于取值,不能用于赋值 ---> 即
user?.age = 23这种写法是不合法的
type Person = {
name: string,
girlFriend?: {
name: string
}
}
const per: Person = {
name: 'Klaus'
}
// per.girlFriend?.name <=> per.girlFirend && per.girlFirend.name
console.log(per.girlFriend?.name) // => undefined
?? 和 !!
const message = 'msg'
// !!message <=> Boolean(message)
console.log(!!message) // true
// ??操作符 --- ES11增加的新特性
// 空值合并操作符(??)是一个逻辑操作符,当操作符的左侧是 null 或者 undefined 时,返回其右侧操作数, 否则返回左侧操作数
let msg: string | null = null
// msg ?? 'default value' 类似于 msg ? msg : 'default value' 或 msg || 'default value'
// 只不过msg为false的条件被限制为了 msg 为 null 和 undefined
// false ?? 'Klaus' => false
console.log(msg ?? 'default value') // => default value
msg = 'Hello World'
console.log(msg ?? 'default value') // => Hello World
字面量类型
let num1 = 123 // num1的类型为 number、
const num2 = 123 // num2的类型为 123 这个123就是字面量类型 也就会num2只能被赋值为123,不可以被赋值为其它的值
let num: 123
num = 123 // success
// num = 234 // error
但是实际开发中,这么做似乎是没有任何意义的,在实际开发中,字面量类型实际上是和联合类型一起组合使用
type Alignment = 'left' | 'right' | 'center'
const align: Alignment = 'left'
字面量推导
type Method = 'POST' | 'PUT' | 'GET' | 'UPDATE'
let request = {
url: 'https://www.example.com/updateUser',
method: 'POST'
}
/*
此时request这个对象的类型会被推导为
{
url: string,
method: string
}
*/
function fetchInfo(url: string, method: Method) {
// ...
}
// 此时request.method会报错的,因为此时request.method的类型是string类型,不是Method类型
fetchInfo(request.url, request.method)
type Method = 'POST' | 'PUT' | 'GET' | 'UPDATE'
let request = {
url: 'https://www.example.com/updateUser',
method: 'POST'
}
function fetchInfo(url: string, method: Method) {
// ...
}
// 解决方法1: 类型断言 最常用 "大类型" -> "小类型"
fetchInfo(request.url, request.method as Method)
type Method = 'POST' | 'PUT' | 'GET' | 'UPDATE'
// 解决方式2: 字面量推导
let request = {
url: 'https://www.example.com/updateUser',
method: 'POST'
} as const
/*
request的类型会被推导为字面量的形式
let request: {
readonly url: "https://www.example.com/updateUser";
readonly method: "POST";
}
*/
function fetchInfo(url: string, method: Method) {
// ...
}
fetchInfo(request.url, request.method )
type Method = 'POST' | 'PUT' | 'GET' | 'UPDATE'
// 解决方式3: 使用手动类型注解替换默认的类型注解
type requestType = {
url: string,
method: Method
}
let request: requestType = {
url: 'https://www.example.com/updateUser',
method: 'POST'
}
function fetchInfo(url: string, method: Method) {
// ...
}
fetchInfo(request.url, request.method )
类型缩小
类型缩小的英文是 Type Narrowing
可以通过类似于 typeof padding === "number" 的判断语句,来改变TypeScript的执行路径
在给定的执行路径中,我们可以缩小比声明时更小的类型,这个过程称之为 缩小
而我们编写的 typeof padding === "number 可以称之为 类型保护(type guards)
常见的类型缩小有以下几种方式:
- typeof
- 平等缩小(比如
===、!==,==,!=,switch) - instanceof 和 xxx.constructor
- in
- ...
typeof
function print(val: number | string) {
if (typeof val === 'number') {
console.log(val.toString())
} else {
console.log(val.toUpperCase())
}
}
平等缩小
type Direction = 'left' | 'right' | 'up' | 'down'
function checkDirection(direction: Direction) {
if (direction === 'left') {
console.log('left', direction)
} else if (direction === 'right') {
console.log('right', direction)
}
// ... 后边的逻辑判断省略
}
instanceof 和 xxx.constructor
class Teacher {
teach() {
console.log('teaching')
}
}
class Student {
learn() {
console.log('learning')
}
}
// 一般使用instanceof 很少使用xxx.constructor
function getAction(per: Teacher | Student) {
if (per instanceof Teacher) {
per.teach()
} else {
per.learn()
}
}
function checkAction(per: Teacher | Student) {
if (per.constructor === Teacher) {
per.teach()
} else if (per.constructor === Student) {
// xxx.constructor 在这里并不能将per.constructor在else中直接判断为Student
// 所以这里必须使用else if 而不是else
per.learn()
}
}
in
type Fish = {
swim: () => void
}
type Cat = {
run: () => void
}
// 这是只是字面量, 不是对象的实例
const fish: Fish = {
swim() {
console.log('swim')
}
}
const cat: Cat = {
run() {
console.log('run')
}
}
function getAction(animal: Cat | Fish) {
if ('swim' in animal) {
// 方法或属性 in 对象 --- 对象中是否有对应的属性或方法 --- 这是JS的语法特性,不是ts的
animal.swim()
} else {
animal.run()
}
}
函数类型
在JavaScript开发中,函数是重要的组成部分,并且函数可以作为一等公民(可以作为参数,也可以作为返回值进 行传递)
在JavaScript中,函数可以作为一个基本的特殊变量来进行使用,所以函数也是需要有自己的类型注解
// () => void --- 就是函数的类型注解
function bar(fn: () => void) { fn() }
// ps:(num1: number, num2: number) => number 中 num1 和 num2 看起来是没有作用的
// 但是是不可以省略的, 如果写成了 (number, number) => number
// 那么对应的类型就会被认定为 (number: any, number: any) => number 从而出现类型错误
type SumFnType = (num1: number, num2: number) => number
const sum: SumFnType = (num1: number, num2: number) => num1 + num2
// callback在被调用的时候,不需要指定参数的类型 会自动进行类型推断
[1, 2, 3].forEach(item => console.log(item))
// ? 就是一个可选参数 --- 可选参数必须放置在普通参数的后边
function print(num1: number, num2?: number) {
console.log(num1, num2)
}
// num1 是有默认值的参数
// 默认参数推荐放置在最后,但是这并不是强制的
function print(num1: number = 10, num2: number) {
console.log(num1, num2)
}
// 如果默认参数不是在最后的话,需要在默认参数对应的位置设置undefined作为占位符
print(undefined, 20)
/*
...nums: number[] 是剩余参数,所有传入的剩余参数会被存放到nums这个数组中
剩余参数必须是最后一个参数
*/
function sum(initalNum: number, ...nums: number[]) {
// ...
}
this
可以正常推导出this的类型
let user = {
name: 'Klaus',
say() {
// 这个函数是被定义在对象内部的
// 所以这个this会被默认推导为是user这个对象
console.log(this.name)
}
}
user.say()
无法正常推导出this的类型
默认情况下,在
tsconfig.json中有一个配置项noImplicitThis
noImplicitThis这个配置项默认是开启的,
noImplicitThis这个配置项就是开启ts对于this的类型检测如果
noImplicitThis关闭了,ts就不会去对this进行类型检测
function say() {
// 这个时候,ts是无法正常推导出this的类型
// 因为say可以有多种调用方式
// 例如: say() user.say() say.call() say.apply() ...
// 所以在这种情况下,ts就无法正常推导出this的类型
// console.log(this.name) ---> error
}
let user = {
name: 'Klaus',
say
}
type sayThisType = { name: string }
// 这个函数中通过this去调用了name属性
// 所以需要手动指定传入的this是一个含有name属性的对象
function say(this: sayThisType) {
console.log(this.name)
}
let user = {
name: 'Klaus',
say
}
user.say()
say.call({ name: 'Alex' })
// say() ---> error
type sayThisType = { name: string }
// 如果需要传递多个参数的时候,this的类型指定必须为第一个参数
function say(this: sayThisType, age: number) {
console.log(this.name, age)
}
let user = {
name: 'Klaus',
age: 23,
say
}
user.say(user.age)
say.call({ name: 'Alex' }, 23)
函数重载
函数重载(overload)就是由一个函数名,但是参数不同的多个函数组成
在调用的时候,根据不同的参数去调用对应的函数实现逻辑
在函数重载中,参数不同的情况一帮可以被分为个数不同和类型不同
// TS中函数重载 比较特殊 函数的类型定义和实现定义是分开的
// 函数类型定义 --- 又被称之为 重载签名(overload signatures)
function add(num1: number, num2: number): number
function add(num1: string, num2: string): string
// 函数实现定义
// 即便num1和num2的类型在定义的时候被设置为了any
// 但是因为之前有函数类型定义,也就是实现了重载
// 所以在调用的时候会从上往下依次进行匹配
// 如果没有匹配的类型定义,会直接报错
function add(num1: number | string, num2: number | string) {
// 因为有了类型定义,所以只要对num1的类型判断好后,就基本可以确定num2的类型
if (typeof num1 === 'string' && typeof num2 === 'string') {
return num1 + ' ' + num2
} else if (typeof num1 !== 'string' && typeof num2 !== 'string') {
return num1 + num2
}
}
console.log(add('Klaus', 'Wang')) // => Klaus Wang
console.log(add(2, 4)) // => 6
// console.log(add(2, 'Klaus')) ---> error 没有任何一个类型定义和它是匹配的