TypeScript中的类型运算符和类型工具InstanceType

360 阅读5分钟

一、TypeScript中的类型运算符

1、typeof 运算符

typeof 获取变量或表达式的类型

let num = 1

type NumType = typeof num

let a: NumType = 12 // success
a = 'hello' // error
2、keyof 运算符

keyof 获取对象类型的所有键,并将其作为联合类型返回。

interface Dog {
  name: string
  age: number
}

type DogKeys = keyof Dog // 'name' | 'age'

let dog: DogKeys = 'name' // success
let dog2: DogKeys = 'age' // success
let dog3: DogKeys = 'gender' // error
3、方括号运算符

方括号运算符用于从对象类型中获取特定属性的类型。

interface Dog {
  name: string
  age: number
}

type NameType = Dog['name'] // string

const name: NameType = 'dog' // success
const name1: NameType = 123 // error
4、in 运算符

in 运算符有两种用法,第一种是用来确定对象是否包含某个属性名。第二种是用来遍历联合类型的每一个成员类型。

// in 用来确定对象是否包含某个属性名。
const obj = { a: 'hello world' }
'a' in obj ? console.log('find a') : console.log('not find a') // find a
'b' in obj ? console.log('find b') : console.log('not find b') // not find b

// in 用来取出(遍历)联合类型的每一个成员类型。
type Keys = 'name' | 'age'
type Person = {
	[K in Keys]: string
} // 等同于 { name: string, age: string }

const person: Person = {
	name: '张三',
	age: '18'
}
5、extends 运算符

extends 用于约束泛型类型或在条件类型中使用

// 用于约束泛型类型
function identity<T extends number | string>(value: T): T {
	return value
}

identity('123') // success
identity(123) // success
identity(true) // error

// 用于在条件类型中使用
type IsString<T> = T extends string ? true : false

const a = '123'

const isString: IsString<typeof a> = true // success
6、infer 关键字

infer 主要用来定义泛型里面推断出来的类型参数,而不是外部传入的类型参数。例如,在处理函数类型时,你可能希望提取参数类型或返回值类型、提取数组元素类型。也可以提取对象属性类型。

// 提取返回值类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : never

function exampleFunction() {
	return 'Hello, World!'
}

type ExampleReturnType = ReturnType<typeof exampleFunction> // string

const a: ExampleReturnType = 'hello world' //success
const a0: ExampleReturnType = 1 // error

// 提取参数类型
type ArgsType<T> = T extends (...args: (infer R)[]) => string ? R : never

function exampleFunction1(num: number) {
	return 'Hello, World!'
}

type ExampleReturnType1 = ArgsType<typeof exampleFunction1> // string

const a1: ExampleReturnType1 = 1 // success
const a2: ExampleReturnType1 = 'hello world' // error

// 提取数组元素类型
type ElementType<T> = T extends (infer U)[] ? U : never

type StringArray = string[]
type StringElement = ElementType<StringArray> // string

const a3: StringElement = 'hello world' // success
const a4: StringElement = 1 // error
7、is 运算符

is 函数返回布尔值的时候,可以使用is运算符,限定返回值与参数之间的关系。

interface Dog {
  run: () => void
}

interface Pig {
  sleep: () => void
}

function isFish(T: Dog | Pig): T is Dog {
  return (T as Dog).run !== undefined
}
const flag = isFish({ run: () => {} }) // true
const flag2 = isFish({ sleep: () => {} }) // false
8、模板字符串

TypeScript 允许使用模板字符串,构建类型。模板字符串的最大特点,就是内部可以引用其他类型,包含stringnumberbigintbooleannullundefined。如果引入这六种以外的类型会报错。模板字符串也可以引入联合类型,如果引入联合类型则会展开联合类型,如引入多个联合类型则会交叉展开联合类型

type num = 1
type Greeting = `hello ${num}` // "hello 1"

const a: Greeting = `hello 1` // success
const a1: Greeting = `hello 123` // error

// 引入其他类型会报错
type hello = 'hello'
type Obj = { a: "world" }
type T1 = `${hello}` // success
type T2 = `${Obj}` // error

// 展开联合类型
type T = 'A' | 'B'
type U = 'C' | 'D'
type V = `${T}_123_${U}` // "A_123_C"|"A_123_D"|"B_123_C"|"B_123_D"

const a3: V = 'A_123_C' // success
const a4: V = 'B_123_C' // success
const a5: V = 'a_123_c' // error

二、类型工具InstanceType

1、InstanceType 的基本用法

InstanceType是一个内置的高级类型工具,用于获取构造函数的实例类型。它接受一个构造函数类型作为参数,并返回该构造函数创建的实例的类型。

class Dog {
  name: string
  constructor(name: string) {
    this.name = name
  }
  run() {
    console.log(`${this.name} run`)
  }
}
type DogType = InstanceType<typeof Dog>
const Tom: DogType = new Dog('tom')
console.log(Tom.name) // tom
Tom.run() // tom run
2、在vue3+Ts中的使用InstanceType

vue3 + Ts + Element Plus 的项目中,我们经常会使用ref去获取Element Plus组件内部的方法,如果只是正常使用ref没有任何代码提示,非常的难受。这里我们就可以用InstanceType配合typeof来解决这问题。代码如下:

<template>
  <div class="box">
    <el-table ref="tableRef" :data="tableData" row-key="id" style="width: 100%">
      <el-table-column type="selection" width="55" />
      <el-table-column prop="date" label="日期" width="180" />
      <el-table-column prop="name" label="姓名" width="180" />
      <el-table-column prop="address" label="地址" />
    </el-table>
    <el-button type="primary" @click="selectFirst">选中第一行</el-button>
    <el-button type="primary" @click="cancelFirst">取消选中</el-button>
  </div>
</template>

<script lang="ts" setup>
import { reactive, ref } from 'vue'
import { ElTable } from 'element-plus'

const tableData = reactive([
  {
    id: '1',
    date: '2016-05-02',
    name: '王小虎',
    address: '上海市普陀区金沙江路 1518 弄'
  },
  {
    id: '2',
    date: '2016-05-02',
    name: '王小虎',
    address: '上海市普陀区金沙江路 1518 弄'
  }
])

const tableRef = ref<InstanceType<typeof ElTable>>()

const selectFirst = () => {
  tableRef.value?.toggleRowSelection(tableData[0], true)
}
const cancelFirst = () => {
  tableRef.value?.clearSelection()
}
</script>

<style lang="scss" scoped></style>

其他的组件也可以使用该方法,例如ElForm组件。但是ElForm组件Element Plus官方也提供了一个类型别名FormInstance去解决代码提示问题。实现的原理都是一样的,具体可以看Element Plus官方文档和源码。

3、封装自定义Hooks

上面我们介绍了在 vue3 + Ts + Element Plus 的项目中使用InstanceType,但是每次都要重复的写这段代码,非常的麻烦。这里我们需要把它封装成一个公共的Hooks方便我们在项目中使用,减少冗余的代码。

hooks代码如下:

import { ref } from 'vue'
export default function useTypeRef<T extends abstract new (...args: any[]) => any>(_Comp: T) {
	return ref<InstanceType<T>>()
}

T extends 这表示 T 是一个泛型参数,并且它需要满足某种特定的类型条件。

abstract 这表示T 必须是一个抽象类的构造函数类型。

new (...args: any[]) => anynew 表示这是一个构造函数。(...args: any[]) 表示构造函数可以接受任意数量、任意类型的参数。=> any 表示构造函数返回一个任意类型的实例。

<template>
  <div class="box">
    <el-table ref="tableRef" :data="tableData" row-key="id" style="width: 100%">
      <el-table-column type="selection" width="55" />
      <el-table-column prop="date" label="日期" width="180" />
      <el-table-column prop="name" label="姓名" width="180" />
      <el-table-column prop="address" label="地址" />
    </el-table>
    <el-button type="primary" @click="selectFirst">选中第一行</el-button>
    <el-button type="primary" @click="cancelFirst">取消选中</el-button>
  </div>
</template>

<script lang="ts" setup>
import { reactive } from 'vue'
import { ElTable } from 'element-plus'
import useTypeRef from '@/hooks/useTypeRef'

const tableData = reactive([
  {
    id: '1',
    date: '2016-05-02',
    name: '王小虎',
    address: '上海市普陀区金沙江路 1518 弄'
  },
  {
    id: '2',
    date: '2016-05-02',
    name: '王小虎',
    address: '上海市普陀区金沙江路 1518 弄'
  }
])

const tableRef = useTypeRef(ElTable)

const selectFirst = () => {
  tableRef.value?.toggleRowSelection(tableData[0], true)
}
const cancelFirst = () => {
  tableRef.value?.clearSelection()
}
</script>

<style lang="scss" scoped></style>

三、最后

文章在这里就结束了,有任何不准确的地方请多多指正,希望各位大佬勿喷。感谢你的阅读,愿你在技术探索的路上不断前行,一起加油!