最新出炉!一文让你快速上手vue3+ts开发🚀学习、复习两不误!

前言

vue3已经出来三年多了,其中新增了组合式api,并且也提供了更好的TypeScript类型标记支持,之后还推出了<script setup>语法糖,使我们的开发体验进一步提升。不知道大家在使用vue3配合ts开发时,是否能够正确标记对应的类型,以获得更好的类型提升支持。下面精心 整理了一篇使用vue3 + typesctypt + <script setup>开发时,如何正确进行类型标记,包括最新的api使用方法,带你快速上手vue3 + ts开发。

一、给组件的props参数定义类型

1.运行时声明

运行时声明是在代码运行时才会确定参数的类型,在编译阶段不会进行类型检测。

1.1 运行时-简单类型声明

js 复制代码
<script setup lang="ts">
const props = defineProps({
  title: String,
  size: {
    type: String,
    required: true,
    validate(val: string) {
      return ['mini', 'default', 'large'].includes(val)
    }
  }
})

props.title // title?: string | undefine
props.size  // size: string
</script>

这种声明方式基本和选项式api的声明方式一致。

1.2 运行时-复杂类型声明

对于运行时声明,有时候我们需要给一个props参数声明为一个自定义接口类型 ,这时需要借助vue内部提供的PropType工具类型进行类型断言:

ts 复制代码
<script setup lang="ts">
import type { IUser } from './types.ts'
import type { PropType } from 'vue'

const props = defineProps({
  data: {
    type: Object as PropType<IUser>,
    required: true
  }
})

props.data  // data: IUser
</script>

2.基于类型的声明

基于类型的声明在编译保存代码编译阶段就能确定类型,获得更好的类型提示。通过泛型参数来定义props的类型比较常用,通过这种方式定义的类型看起来也更加直观。

ts 复制代码
<script setup lang="ts">
import type { IUser } from './types.ts'
const props = defineProps<{
  title?: string,
  size: 'mini' | 'default' | 'large',
  data: IUser
}>()

props.title // title?: string | undefine
props.size  // size: "default" | "mini" | "large"
props.data  // data: IUser
</script>

以上我们通过泛型定义的参数和上面基于运行时声明参数是一样的,但是明显可以看出这样写简洁了许多。

3.withDefaults定义参数默认值

基于类型的声明在给参数赋默认值时,需要借助withDefaults 编译器宏来实现。

ts 复制代码
<script setup lang="ts">
import type { IUser } from './types.ts'
const props = withDefaults(defineProps<{
  title?: string,
  size: 'mini' | 'default' | 'large',
  data: IUser
}>(), {
  title: '标题',
  size: 'default',
  data: () => {
    return {name: '细狗', age: 18}
  }
})
</script>

注意 :引用数据类型data给默认值时需要通过一个构造方法进行返回,并且给默认值后,原来在定义类型时是必传参,也会自动转化为可选参。

4.父组件传入泛型声明

有时候我们不确定props的参数类型,希望父组件通过传入泛型的方式进行类型定义,这时候我们可以使用 <script> 标签上的 generic 属性声明泛型类型参数:

ts 复制代码
<script setup lang="ts" generic="T, U extends Object">
const props = defineProps<{
  title?: T,
  data: U
}>()

props.title // title?: T | undefined
props.data  // data: U extends Object
</script>

这时,我们在父组件中传递参数时,就能够自动推导出参数对应的类型了:

ts 复制代码
<template>
  <Item :data="user" title="人"></Item>  <!-- data:IUser,title?: string | undefined -->
  <Item :data="dog" :title="0"></Item>  <!-- data:IDog,title?: number | undefined -->
</template>

<script setup lang="ts">
import Item from './Item.vue'
import { ref } from 'vue' 
import type { IUser, IDog } from './types.ts'

const user = ref<IUser>({name: '细狗', age: 18})
const dog = ref<IDog>({name: '舔狗',color: '白色'})
</script>

二、给组件定义的emits事件标记类型

1.运行时声明emits

ts 复制代码
<script lang="ts" setup>
const emit = defineEmits(['change', 'confirm'])
emit  // emit: (event: "change" | "confirm", ...args: any[]) => void
</script>

这种声明方式比较方便,但是无法给触发的事件参数定义类型。如有这个需求,我们可以通过对象字面量的方式解决这个问题。

ts 复制代码
// 基于选项
const emit = defineEmits({
  change: (id: number) => {
    // 返回 `true` 或 `false`
    // 表明验证通过或失败
    if(id !== 1) return false
    return true
  },
  confirm: (_value: string) => {
    //value前面加个_,表示方法内可以忽略使用
    return true
  }
})
// emit  // emit: ((event: "change", id: number) => void) & ((event: "confirm", value: string) => void)
emit('change', 2) // 参数不是1,发出警告[Vue warn]: Invalid event arguments: event validation failed for event "change".
emit('confirm', 'a')
</script>

2.基于类型声明emits

使用这种方式可以方便的定义多个参数类型和事件返回值类型,也是目前来看比较常见的一种方式。

ts 复制代码
const emit = defineEmits<{
  // 无返回值
  (e: 'change', id: number, value: string): void
  (e: 'confirm', value: string): void
}>()

除此之外,vue3.3+,也新出了一种更简洁的语法,当你不关注事件返回值类型时,可以考虑这这种写法:

ts 复制代码
const emit = defineEmits<{
  change: [id: number, value: string]
  confirm: [value: string]
}>()

这和前面声明的emits事件是一样的,都是没有返回值。

三、给ref标记类型

1.使用泛型

ts 复制代码
// 默认值类型推导
const count = ref(0); // count: Ref<number>
// 和上面一个等价
const count = ref<number>(0); // count: Ref<number>
// 不提供默认值时,类型推导会得到一个包含undefined的联合类型
const count = ref<number>(); // count: Ref<number | undefined>

2.Ref工具类型

ts 复制代码
const count: Ref<number> = ref()  // 不能将类型"Ref<number | undefined>"分配给类型"Ref<number>"
// ref()方法返回值类型必须和count定义的类型一致才行
const count: Ref<number | undefined> = ref()

3.给reactive标注类型

ts 复制代码
interface IState {
  name: string
}

// state得到的类型推断为: {name: string}
const state = reactive({
  name: '细狗'
})

// 显式声明类型,等价于上面的类型推导
const state: IState = reactive({
  name: '细狗'
})

四、给computed标记类型

ts 复制代码
const amount = ref(10)
// 隐式推导返回值类型,doubleAmount: ComputedRef<number>
const doubleAmount = computed(() => amount.value * 2)
// 显示定义返回值类型
const doubleAmount = computed<number>(() => amount.value * 2)

五、给provide / inject 标注类型

ts 复制代码
/* 父组件 */
provide('name', 0)

/* 子组件 */
// name没有默认值
const myName = inject<string>('name') // name: string | undefined
// 给name提供默认值
const myName = inject<string>('name', '哈哈') // name: string
console.log(myName) // 0

我们上面再父组件中提供了一个number类型的值,但是子组件中定义要注入的是string类型的值,这样子是不会报错的!为了保证正确的类型,我们需要使用Vue提供了一个 InjectionKey 接口,它是一个继承自 Symbol 的泛型类型,可以用来在提供者和消费者之间同步注入值的类型:

我们先建一个constant.ts保存项目常量的文件,把key定义在里面,这样子就可以父组件和子组件同时引入这个key使用了:

ts 复制代码
/** constant.ts */
import type { InjectionKey  } from 'vue'
export const name = Symbol() as InjectionKey<string>

/** 父组件 */
import { name } from './constant'
// provide(name, 0) // 类型"number"的参数不能赋给类型"string"的参数。
provide(name, '奥特曼')

/** 子组件 */
import { name } from './constant'
const myName = inject<string>(name, '哈哈') // name: string
console.log(myName) // 奥特曼

六、给组件ref引用标记类型

当我们在自定义组件上绑定一个ref值时,如果需要编辑器能够正确的提示组件内暴露的属性,则需要正确设置组件的ref类型,下面来看一个例子

ts 复制代码
<!-- Item.vue -->
<script lang="ts" setup>
import { ref } from 'vue'

const show = ref(false)
const summit = () => {
  console.log('提交')
}
const reset = () => {
  console.log('重置')
}

// 向外暴露属性或方法
defineExpose({
  show,
  summit,
  reset
})
</script>
ts 复制代码
<!-- Parent.vue -->
<template>
  <Item ref="Item"></Item>
</template>

<script setup lang="ts">
import Item from "./Item.vue";
import { ref} from "vue";

const ItemRef = ref(null) // ItemRef: Ref<null>

以上没有给ref传递泛型,所以使用ItemRef时将得不到任何提示,因为是一个null类型,如下图所示

为了得到Item的类型,我们首先需要通过TypeScript中的typeof得到Item组件的类型,再使用TypeScript内置的InstanceType工具类型来获取其实例类型:

ts 复制代码
<script setup lang="ts">
import Item from "./Item.vue";
import { ref} from "vue";

const ItemRef = ref<InstanceType<typeof Item> | null>(null)
</script>

这样我们就能得到正确的类型提示了!

如何你只希望读取组件的$ref实例,或者所有组件都共享的属性,不关心其暴露值,也可以使用vue内部提供的ComponentPublicInstance类型

ts 复制代码
import type { ComponentPublicInstance } from 'vue'
const ItemRef = ref<ComponentPublicInstance | null>(null)

总结

以上在定义组件props类型时,分别提到了运行时声明和基于类型的声明的用法,在使用泛型声明类型时,如果需要给参数默认值,则需要使用withDefaults编译宏,进行赋值;除此之外,还说明了如何定义通过父组件传递参数类型的组件。接着说明emits事件也支持运行时类型和基于类型的定义方式,其中还提到了vue3.3+新增的一种更为简洁的写法;接着我们简单介绍了ref、reactive、computed的类型定义方式及一些注意事项;之后说明了在使用provide/inject时,如何保证提供和注入类型的一致性 ;最后在使用自定义组件时,通过InstanceType<typeof 组件>的方式正确设置组件类型,以获得更好的类型提示支持。

相关推荐
Darling02zjh23 分钟前
GUI图形化演示
前端
Channing Lewis25 分钟前
如何判断一个网站后端是用什么语言写的
前端·数据库·python
互联网搬砖老肖36 分钟前
Web 架构之状态码全解
前端·架构
showmethetime43 分钟前
matlab提取脑电数据的五种频域特征指标数值
前端·人工智能·matlab
码农捻旧1 小时前
解决Mongoose “Cannot overwrite model once compiled“ 错误的完整指南
javascript·数据库·mongodb·node.js·express
淡笑沐白1 小时前
探索Turn.js:打造惊艳的3D翻页效果
javascript·html5·turn.js
海上彼尚1 小时前
秒删node_modules[无废话版]
vue.js·react.js
sunxunyong2 小时前
yarn任务筛选spark任务,判断内存/CPU使用超过限制任务
javascript·ajax·spark
Ynov2 小时前
详细解释api
javascript·visual studio code
左钦杨2 小时前
IOS CSS3 right transformX 动画卡顿 回弹
前端·ios·css3