TypeScript
TypeScript
常用的 TS 类型定义
原始数据类型
原始数据类型 | JavaScript | TypeScript |
---|---|---|
字符串 | String | string |
数值 | Number | number |
布尔值 | Boolean | boolean |
大整数 | BigInt | bigint |
符号 | Symbol | symbol |
不存在 | Null | null |
未定义 | Undefined | undefined |
// 字符串
const str: string = 'Hello World'
// 数值
const num: number = 1
// 布尔值
const bool: boolean = true
不过在实际的编程过程中,原始数据类型的类型定义是可以省略的
,因为 TypeScript 会根据声明变量时赋值的类型,自动帮推导变量类型
,也就是可以跟平时写 JavaScript 一样:
// 这样也不会报错,因为 TS 会帮推导它们的类型
const str = 'Hello World'
const num = 1
const bool = true
数组
除了原始数据类型之外, JavaScript 还有引用类型,数组 Array 就是其中的一种。
数组里的数据 | 类型写法 1 | 类型写法 2 |
---|---|---|
字符串 | string[] | Array |
数值 | number[] | Array |
布尔值 | boolean[] | Array |
大整数 | bigint[] | Array |
符号 | symbol[] | Array |
不存在 | null[] | Array |
未定义 | undefined[] | Array |
另外一种写法是基于 TS 的泛型
Array<T>
如果的数组一开始就有初始数据(数组长度不为 0 ),那么 TypeScript 也会根据数组里面的项目类型,正确自动帮推导这个数组的类型,这种情况下也可以省略类型定义
// 这种有初始项目的数组, TS 也会帮推导它们的类型
const strs = ['Hello World', 'Hi World']
const nums = [1, 2, 3]
const bools = [true, true, false]
但是!如果一开始是 []
,那么就必须显式的指定数组类型:
// 这个时候会认为是 any[] 或者 never[] 类型
const nums = []
// 这个时候再 push 一个 number 数据进去,也不会使其成为 number[]
nums.push(1)
而对于复杂的数组,比如数组里面的 item 都是对象,其实格式也是一样,只不过把原始数据类型换成 对象的类型 即可,例如 UserItem[] 表示这是一个关于用户的数组列表。
对象(接口)
如何定义对象的类型
对象的类型定义有两个语法支持: type
和 interface
先看看 type
的写法:
ts
type UserItem = {
// ...
}
再看看 interface
的写法:
ts
interface UserItem {
// ...
}
可以看到它们表面上的区别是一个有
=
号,一个没有
TIP
对象的类型定义通常采用 Upper Camel Case 大驼峰命名法,也就是每个单词的首字母大写,例如
UserItem
、GameDetail
,这是为了跟普通变量进行区分(变量通常使用 Lower Camel Case 小驼峰写法,也就是第一个单词的首字母小写,其他首字母大写,例如userItem
)。
// 定义用户对象的类型
interface UserItem {
name: string
age: number
}
// 在声明变量的时候将其关联到类型上
const petter: UserItem = {
name: 'Petter',
age: 20,
}
注意,上面这样定义的接口类型,表示 name
和 age
都是必选的属性,不可以缺少,一旦缺少,代码运行起来就会报错!
可选的接口属性
age
后面紧跟了一个 ?
号再接 :
号,这是 TypeScript 对象对于可选属性的一个定义方式
interface UserItem {
name: string
// 这个属性变成了可选
age?: number
}
const petter: UserItem = {
name: 'Petter',
}
调用自身接口的属性
如果一些属性的结构跟本身一致,也可以直接引用,比如下面例子里的 friendList
属性,用户的好友列表,它就可以继续使用 UserItem
这个接口作为数组的类型:
interface UserItem {
name: string
age: number
enjoyFoods: string[]
// 这个属性引用了本身的类型
friendList: UserItem[]
}
const petter: UserItem = {
name: 'Petter',
age: 18,
enjoyFoods: ['rice', 'noodle', 'pizza'],
friendList: [
{
name: 'Marry',
age: 16,
enjoyFoods: ['pizza', 'ice cream'],
friendList: [],
},
{
name: 'Tom',
age: 20,
enjoyFoods: ['chicken', 'cake'],
friendList: [],
}
],
}
接口的继承
接口还可以继承,比如要对用户设置管理员,管理员信息也是一个对象,但要比普通用户多一个权限级别的属性,那么就可以使用继承,它通过 extends
来实现:
interface UserItem {
name: string
age: number
enjoyFoods: string[]
friendList: UserItem[]
}
// 这里继承了 UserItem 的所有属性类型,并追加了一个权限等级属性
interface Admin extends UserItem {
permissionLevel: number
}
const admin: Admin = {
name: 'Petter',
age: 18,
enjoyFoods: ['rice', 'noodle', 'pizza'],
friendList: [
{
name: 'Marry',
age: 16,
enjoyFoods: ['pizza', 'ice cream'],
friendList: [],
},
{
name: 'Tom',
age: 20,
enjoyFoods: ['chicken', 'cake'],
friendList: [],
}
],
permissionLevel: 1,
}
如果觉得这个 Admin
类型不需要记录这么多属性,也可以在继承的过程中舍弃某些属性,通过 Omit
帮助类型来实现,Omit
的类型如下:
type Omit<T, K extends string | number | symbol>
其中
T
代表已有的一个对象类型,K
代表要删除的属性名,如果只有一个属性就直接是一个字符串,如果有多个属性,用|
来分隔开,下面的例子就是删除了两个不需要的属性:
interface UserItem {
name: string
age: number
enjoyFoods: string[]
friendList?: UserItem[]
}
// 这里在继承 UserItem 类型的时候,删除了两个多余的属性
interface Admin extends Omit<UserItem, 'enjoyFoods' | 'friendList'> {
permissionLevel: number
}
// 现在的 admin 就非常精简了
const admin: Admin = {
name: 'Petter',
age: 18,
permissionLevel: 1,
}
类
在 TypeScript ,通过类得到的变量,它的类型就是这个类:
// 定义一个类
class User {
// constructor 上的数据需要先这样定好类型
name: string
// 入参也要定义类型
constructor(userName: string) {
this.name = userName
}
getName() {
console.log(this.name)
}
}
// 通过 new 这个类得到的变量,它的类型就是这个类
const petter: User = new User('Petter')
petter.getName() // Petter
类也可以提供给接口去继承:
// 这是一个类
class UserBase {
name: string
constructor(userName: string) {
this.name = userName
}
}
// 这是一个接口,可以继承自类
interface User extends UserBase {
age: number
}
// 这样这个变量就必须同时存在两个属性
const petter: User = {
name: 'Petter',
age: 18,
}
如果类上面本身有方法存在,接口在继承的时候也要相应的实现,当然也可以借助在 对象(接口) 提到的 Omit 帮助类型来去掉这些方法。
class UserBase {
name: string
constructor(userName: string) {
this.name = userName
}
// 这是一个方法
getName() {
console.log(this.name)
}
}
// 接口继承类的时候也可以去掉类上面的方法
interface User extends Omit<UserBase, 'getName'> {
age: number
}
// 最终只保留数据属性,不带有方法
const petter: User = {
name: 'Petter',
age: 18,
}
联合类型
当一个变量可能出现多种类型的值的时候,可以使用联合类型来定义它,类型之间用 |
符号分隔。
Vue 的路由在不同的数据结构里也有不同的类型,有时候需要通过路由实例来判断是否符合要求的页面,也需要用到这种联合类型:
// 注意:这不是完整的代码,只是一个使用场景示例
import type { RouteRecordRaw, RouteLocationNormalizedLoaded } from 'vue-router'
function isArticle(
route: RouteRecordRaw | RouteLocationNormalizedLoaded
): boolean {
// ...
}
用 Vue 做页面,会涉及到子组件或者 DOM 的操作,当它们还没有渲染出来时,获取到的是 null ,渲染后才能拿到组件或者 DOM 结构,这种场景也可以使用联合类型:
// querySelector 拿不到 DOM 的时候返回 null
const ele: HTMLElement | null = document.querySelector('.main')
函数
函数两个最核心的操作:输入与输出,也就是对应函数的 “入参” 和 “返回值” ,在 TypeScript ,函数本身和 TS 类型有关系的也是在这两个地方。
函数的入参是把类型写在参数后面,返回值是写在圆括号后面:
// 注意:这是 TypeScript 代码
// 写法一:函数声明
function sum1(x: number, y: number): number {
return x + y
}
// 写法二:函数表达式
const sum2 = function(x: number, y: number): number {
return x + y
}
// 写法三:箭头函数
const sum3 = (x: number, y: number): number => x + y
// 写法四:对象上的方法
const obj = {
sum4(x: number, y: number): number {
return x + y
}
}
函数的可选参数
// 注意 isDouble 这个入参后面有个 ? 号,表示可选
function sum(x: number, y: number, isDouble?: boolean): number {
return isDouble ? (x + y) * 2 : x + y
}
// 这样传参都不会报错,因为第三个参数是可选的
sum(1, 2) // 3
sum(1, 2, true) // 6
TIP
需要注意的是,可选参数必须排在必传参数的后面。
无返回值的函数
这种函数用 void
来定义它的返回,也就是空。
// 注意这里的返回值类型
function sayHi(name: string): void {
console.log(`Hi, ${name}!`)
}
sayHi('Petter') // Hi, Petter!
需要注意的是, void
和 null
、 undefined
不可以混用,如果的函数返回值类型是 null
,那么是真的需要 return
一个 null
值
// 只有返回 null 值才能定义返回类型为 null
function sayHi(name: string): null {
console.log(`Hi, ${name}!`)
return null
}
异步函数的返回值
对于异步函数,需要用 Promise<T>
类型来定义它的返回值,这里的 T
是泛型,取决于的函数最终返回一个什么样的值( async / await
也适用这个类型)。
例如这个例子,这是一个异步函数,会 resolve
一个字符串,所以它的返回类型是 Promise<string>
(假如没有 resolve
数据,那么就是 Promise<void>
)
// 注意这里的返回值类型
function queryData(): Promise<string> {
return new Promise((resolve) => {
setTimeout(() => {
resolve('Hello World')
}, 3000)
})
}
queryData().then((data) => console.log(data))
函数本身的类型
通过函数表达式或者箭头函数声明的函数,这样写好像只对函数体的类型进行了定义,而左边的变量并没有指定。
没错,确实是没有为这个变量指定类型:
// 这里的 sum ,确实是没有指定类型
const sum = (x: number, y: number): number => x + y
这是因为,通常 TypeScript 会根据函数体帮自动推导,所以可以省略这里的定义。
如果确实有必要,可以这样来定义等号左边的类型:
ts
const sum: (x: number, y: number) => number = (x: number, y: number): number =>
x + y
这里出现了 2 个箭头 =>
,注意第一个箭头是 TypeScript 的,第二个箭头是 JavaScript ES6 的。
实际上上面这句代码是分成了三部分:
const sum: (x: number, y: number) => number
是这个函数的名称和类型= (x: number, y: number)
这里是指明了函数的入参和类型: number => x + y
这里是函数的返回值和类型
TypeScript 的函数类型是以 () => void
这样的形式来写的:左侧圆括号是函数的入参类型,如果没有参数,就只有一个圆括号,如果有参数,就按照参数的类型写进去;右侧则是函数的返回值。
事实上由于 TypeScript 会帮推导函数类型,所以很少会显式的去写出来,除非在给对象定义方法
// 对象的接口
interface Obj {
// 上面的方法就需要显式的定义出来
sum: (x: number, y: number) => number
}
// 声明一个对象
const obj: Obj = {
sum(x: number, y: number): number {
return x + y
}
}