vue3中常用的API的TypeScript类型注解

211 阅读3分钟

前言

在开发vue3的过程中,总会有那么一些方法忘记了怎么去写ts的类型注解,所以稍微整理一下vue中使用到的ts类型注解。另外本文章只考虑composition API也就是组合式API。

ref

一般情况下

// 推导出来的类型: Ref<number>
const num = ref(888)

当给定一个初始值的时候,ref会自动推导它的类型,当没有赋值的时候ref(),类型则是any

泛型参数

// 推导出来的类型: Ref<number | string>
const year = ref<number | string>(2023)

year.value = '2023' // ok

当指定了泛型参数,但是没有给定初始值ref<number>()的时候,那么得到的将是一个包含undefined的联合类型:

// 推导出来的类型: Ref<number | undefined>
const num = ref<number>()

也可以传入一个比较复杂的类型

  interface List {
    id: number,
    title: string,
    content: string
  }
// 推导出来的类型: Ref<List | undefined>
  const list = ref<List>()

reactive

一般情况下

// 推导得到的类型:{ title: string } 
const book = reactive({ title: '标题' })

指定类型

interface Book {
    title: string
    year?: number 
}
const book: Book = reactive({ title: '标题' })

不推荐使用泛型参数,因为处理深层次ref解包的返回值和泛型参数的类型不同

上面这句是什么意思呢?为什么reactive()不推荐使用泛型来指定类型呢?举个例子:

interface Book {
  title: string
  year?: number
}
const book = reactive<Book>({ title: '标题' })

这样是没有问题的,但是如果reactive中有使用ref类型的话,例如以下情况:

interface Book {
  title: string
  year?: number
}
const title = ref("我是标题")
// 报错:Type 'Ref<string>' is not assignable to type 'string'.
const book = reactive<Book>({ title: title })

那如果把title的类型定义成Ref<string>呢?如:

interface Book {
  title: Ref<string>
  year?: number
}
const title = ref("我是标题")
const book = reactive<Book>({ title: title })

但你会发现,book的类型会变成

const book: {  
    title: string;  
    year?: number;  
}

title的类型是string并不是定义的Ref<string>,所以reactive带有深层次的ref的时候,如果通过泛型来约束类型,类型会对应不上的。

computed

computedref一样都是通过泛型来指定类型,如果不指定类型那么会自动推导类型

const double = computed<number>(() => {
  // 若返回值不是 number 类型则会报错
})

defineProps

运行时声明

const props = defineProps({
  id: {type: Number, required: true},
  title: String
})
props.id // number
props.title // string | undefined

运行时声明就是沿用js的那一套,然而通过泛型参数来定义props的类型通常更加直接:

const props = defineProps<{
  id: number,
  title?: string
}>()

需要注意的是:在 3.2 及以下版本中,defineProps() 的泛型类型参数仅限于类型文字或对本地接口的引用。 这个限制在 3.3 中得到了解决。最新版本的 Vue 支持在类型参数位置引用导入和有限的复杂类型。但是,由于类型到运行时转换仍然基于 AST,一些需要实际类型分析的复杂类型,例如条件类型,还未支持。您可以使用条件类型来指定单个 prop 的类型,但不能用于整个 props 对象的类型。

不过我们发现一个问题,怎么定义它的默认值呢?使用withDefaults来定义默认值

interface Props {
  id: number,
  title?: string
}

const props = withDefaults(
  defineProps<Props>(), {
  id: 1,
  title: "我是标题"
})

defineEmits

在 <script setup> 中,emit 函数的类型标注也可以通过运行时声明或是类型声明进行:

<script setup lang="ts">
// 运行时
const emit = defineEmits(['change', 'update'])

// 基于类型
const emit = defineEmits<{
  (e: 'change', id: number): void
  (e: 'update', value: string): void
}>()

// 3.3+: 可选的、更简洁的语法
const emit = defineEmits<{
  change: [id: number]
  update: [value: string]
}>()
</script>