TS

105 阅读6分钟

原始类型

let age: number = 18
let myName: string = '老师'
let isLoading: boolean = false

数组类型

// 写法一:
let numbers: number[] = [1, 3, 5]
// 写法二:
let strings: Array<string> = ['a', 'b', 'c']

联合类型

// 通过联合类型将多个类型组合成一个类型
let timer: number | null = null
  timer = setInterval(() => {}, 1000)

  // 定义一个数组,数组中可以有数字或者字符串, 需要注意 | 的优先级
  let arr: (number | string)[] = [1, 'abc', 2]

类型别名 使用场景:当同一类型(复杂)被多次使用时,可以通过类型别名,简化该类型的使用

type CustomArray = (number | string)[]

let arr1: CustomArray = [1, 'a', 3, 'b']
let arr2: CustomArray = ['x', 'y', 6, 7]

函数类型 函数的类型实际上指的是:函数参数返回值的类型

  • 为函数指定类型的两种方式:
    1. 单独指定参数、返回值的类型
    2. 同时指定参数、返回值的类型
1. 单独指定参数、返回值的类型:

```ts
// 函数声明
function add(num1: number, num2: number): number {
  return num1 + num2
}

// 箭头函数
const add = (num1: number, num2: number): number => {
  return num1 + num2
}

2. 同时指定参数、返回值的类型:

type AddFn = (num1: number, num2: number) => number

const add: AddFn = (num1, num2) => {
  return num1 + num2
}

void 类型

function greet(name: string): void {
  console.log('Hello', name)
}
如果指定 返回值类型为 undefined,此时,函数体中必须显示的 return undefined 才可以
const add = (): undefined => {
  // 此处,返回的 undefined 是 JS 中的一个值
  return undefined
}

可选参数

// 参数可以传也可以不传
function mySlice(start?: number, end?: number): void {
  console.log('起始索引:', start, '结束索引:', end)
}

对象类型

// 空对象
let person: {} = {}

// 有属性的对象
let person: { name: string } = {
  name: '同学'
}

// 既有属性又有方法的对象
// 在一行代码中指定对象的多个属性类型时,使用 `;`(分号)来分隔
let person: { name: string; sayHi(): void } = {
  name: 'jack',
  sayHi() {}
}

// 对象中如果有多个类型,可以换行写:
// 通过换行来分隔多个属性类型,可以去掉 `;`
let person: {
  name: string
  sayHi(): void
} = {
  name: 'jack',
  sayHi() {}
}

箭头函数形式的方法类型

type Person = {
  greet: (name: string) => void
  greet(name: string):void
}
// - 注意:直接使用 `{}` 形式为对象添加类型,会降低代码的可读性(不好辨识类型和值)
let person: {
  name: string
  sayHi(): void
} = {
  name: 'jack',
  sayHi() {}
}

//  推荐:**使用类型别名为对象添加类型**
let person: Person = {
  greet(name) {
    console.log(name)
  }
}

接口类型

interface IPerson {
  name: string
  age: number
  sayHi(): void
}


let person: IPerson = {
  name: 'jack',
  age: 19,
  sayHi() {}
}
### interface vs type

- interface(接口)和 type(类型别名)的对比:
- 相同点:都可以给对象指定类型
- 不同点:
  - 接口,只能为对象指定类型
  - 类型别名,不仅可以为对象指定类型,实际上可以为任意类型指定别名
- 推荐:**能使用 type 就是用 type**

那为什么要用呢?
### 接口继承
 如果两个接口之间有相同的属性或方法,可以将公共的属性或方法抽离出来,通过继承来实现复用
 interface Point2D { x: number; y: number }
// 继承 Point2D
interface Point3D extends Point2D {
  z: number
}
// 继承后,Point3D 就有了 Point2D 的所有属性和方法(此时,Point3D 同时有 x、y、z 三个属性)

元组类型

元组类型是另一种类型的数组,它确切地知道包含多少个元素,**以及特定索引对应的类型**
let position: [number, number] = [39.5427, 116.2317]
  1. 元组类型可以确切地标记出有多少个元素,以及每个元素的类型
  2. 该示例中,元素有两个元素,每个元素的类型都是 number

字面量类型

字面量类型配合联合类型一起使用
type Direction = 'up' | 'down' | 'left' | 'right'

function changeDirection(direction: Direction) {
  console.log(direction)
}

枚举类型

定义一组命名常量。它描述一个值,该值可以是这些命名常量中的一个
// 创建枚举
enum Direction { Up, Down, Left, Right }

// 使用枚举类型
function changeDirection(direction: Direction) {
  console.log(direction)
}

类型断言

const aLink = document.getElementById('link') as HTMLAnchorElement
or
const aLink = <HTMLAnchorElement>document.getElementById('link')
 // 关键字 as 后面的类型是一个更加具体的类型(HTMLAnchorElement 是 HTMLElement 的子类型)
 // 通过类型断言,aLink 的类型变得更加具体,这样就可以访问 a 标签特有的属性或方法了

泛型

//泛型在保证类型安全(不丢失类型信息)的同时,可以让函数等与多种不同的类型一起工作,灵活可复用
//调用函数的时候再定义类型,确保参数和返回值类型相同
// 定义函数
function id<Type>(value: Type): Type { return value }

function id<T>(value: T): T { return value }
// 调用函数,尖括号中指定具体的类型
const num = id<number>(10)
const str = id<string>('a')

// 简化泛型函数调用
let num = id(10)
let str = id('a')
// 传入实参 10,TS 会自动推断出变量 num 的类型 number,并作为 Type 的类型

泛型约束

// 泛型函数的类型变量 Type 可以代表多个类型,这导致无法访问任何属性
// 将类型修改为 `Type[]`(Type 类型的数组),因为只要是数组就一定存在 length 属性,因此就可以访问了
function id<Type>(value: Type[]): Type[] {
  console.log(value.length)
  return value
}
## 添加约束
// 创建一个接口
interface ILength { length: number }

// 表示传入的 类型 必须满足 ILength 接口的要求才行,也就是得有一个 number 类型的 length 属性
function id<Type extends ILength>(value: Type): Type {
  console.log(value.length)
  return value
}
// 通过 `extends` 关键字使用该接口,为泛型(类型变量)添加约束
// 该约束表示:**传入的类型必须具有 length 属性

多个类型变量

// 泛型的类型变量可以有多个,并且**类型变量之间还可以约束**(比如,第二个类型变量受第一个类型变量约束)

// Type extends object 表示: Type 应该是一个对象类型,如果不是 对象 类型,就会报错
// keyof 关键字接收一个对象类型,生成其键名称(可能是字符串或数字)的联合类型
// 类型变量 Key 受 Type 约束,可以理解为:Key 只能是 Type 所有键中的任意一个,或者说只能访问对象中存在的属性
function getProperty<Type extends object, Key extends keyof Type>(obj: Type, key: Key) {
  return obj[key]
}

泛型接口

// 定义接口时先使用泛型,等调用接口时再定义类型
interface IdFunc<Type> {
  id: (value: Type) => Type
  ids: () => Type[]
}
// 定义函数
let obj: IdFunc<number> = {
  id(value) { return value },
  ids() { return [1, 3, 5] }
}

ts + vue3

defineProps

// defineProps配合ts的泛型定义props类型校验
defineProps<{
  money: number
  car?: string
}>()

const { money, car = '小黄车' } = defineProps<{
  money: number
  car?: string
}>()

defineEmits

const emit = defineEmits<{
  (e: 'changeMoney', money: number): void
  (e: 'changeCar', car: string): void
}>()

ref

// 简单类型不推荐定义

// 复杂类型推荐使用泛型
type Todo = {
  id: number
  name: string
  done: boolean
}

const list = ref<Todo[]>([])

setTimeout(() => {
  list.value = [
    { id: 1, name: '吃饭', done: false },
    { id: 2, name: '睡觉', done: true }
  ]
})
// dom
const imgRef = ref<HTMLImageElement | null>(null)

computed

// 通过泛型可以指定computed计算属性的类型,通常可以省略
const leftCount = computed<number>(() => {
  return list.value.filter((item) => item.done).length
})

事件处理

const move = (e: MouseEvent) => {
  mouse.value.x = e.pageX
  mouse.value.y = e.pageY
}

<h1 @mousemove="move($event)">根组件</h1>

非空断言

// 告诉typescript, 明确的指定obj不可能为空
let nestedProp = obj!.second;