巧妙使用Vue3泛型组件,提升你的组件使用体验

早在 Vue3.3 版本开始,Vue3 开始支持泛型组件。可笔者在写这篇文章的时候,依然发现一些群友在 Vue3 的项目中还没有用上,于是今天出一篇文章,和大家一起交流。

什么是泛型

泛型编程是一种编程范式,允许你编写能够与多种数据类型一起工作的代码,同时保留编译时类型检查的益处。通过使用泛型,你可以创建类、接口和方法,这些组件可以操作不特定于某一个类型的对象。

如上所说,泛型可以用在类、接口、方法、属性中,那自然也能用在组件中。

于是 Vue3 在组件中支持了泛型参数,让我们能更好的传递参数类型,从而提升我们的组件使用体验。

泛型组件的优点

定义和使用泛型组件,你可以体验到:

限制props参数的类型

可以限制传入组件的数据必须是某个类或者某个接口的继承者的类型,于是你可以在组件内部放心大胆的调用该类型下应该有的属性和方法等。

例如限制了传入 User 的类型,那么组件内部可以大胆的调用用户的 昵称、性别等信息。

提供emit事件的属性类型

组件我们往往还会提供一系列的事件,而事件一般都是通过 emit 来传递的,那么组件内部就可以通过泛型来定义事件类型,使用者在获取 emit 属性的时候,可以无需断言,直接依赖泛型传递的参数类型进行接下来的操作。

例如 一个组件要求传入了一个用户列表,那组件的点击事件会返回列表中点击的用户信息,@click 事件的参数就可以直接拿到是用户的类型。

提供slot插槽的参数类型

组件提供插槽之后,可能会给插槽传递一些作用域带参数的类型,我们也可以用泛型组件来直接传递。

如何定义泛型组件

基于上面的三点,我们来定义一个卡片列表组件:

泛型组件和基础类型定义

首先,我们定义了一个 Item 接口,用于定义传入组件的数据基础类型。

ts 复制代码
export interface Item{
  id: number
  disabled: boolean
  description?: string
}

其中包含了必要的 ID禁用 以及可选的 描述 三个属性。

为了方便测试,我们起一个 Item 的子类型:

ts 复制代码
/**
 * # 用户
 */
export interface User extends Item {
  /**
   * ## 昵称
   */
  name: string

  /**
   * ## 年龄
   */
  age: number
}

接下来,我们定义了一个泛型组件,并使用 generic 关键字来指定组件的泛型类型。

html 复制代码
<script lang="ts" setup generic="T extends Item">

</script>

这表示,这个组件是个泛型组件,它的类型参数 T 必须是 Item 类型或子类型。

html 复制代码
<script lang="ts" setup>
const users: Ref<User[]> = ref([
  {
    // 用户信息
  }
])
</script>
<template>
  <card-list :items="users"/>
</template>

定义Props参数

我们在定义了泛型组件之后,就可以在组件的 props 中使用泛型类型了。

ts 复制代码
defineProps({
  items: {
    type: Array<T>,
    required: true,
  }
})

上面的代码表示,这个组件必须传入 :items="[]" 的数组,并且这个数组中的每一项都必须是 Item 类型或者它的子类型。

定义Emit事件

定义Emit事件,我们使用 defineEmits 来定义。

ts 复制代码
const emits = defineEmits<{
  click:[T]
}>()

这里我们定义了一个名为 click 的事件,它的参数类型是 T,表示点击事件会返回一个 Item 类型的对象。

所以我们就可以在组件内部通过 @click="emits('click', item)" 来传递事件,并且 item 的类型就是 T

定义Slot插槽

我们通过 slot 来定义插槽,并使用泛型类型来定义插槽的参数类型。

html 复制代码
<slot name="title" :row="item" />

这时,插槽就带上了一个 row 属性,而且属性的类型和 item 一致,都是传入的 :items 的真实类型。

使用的时候,我们就可以直接拿到 row 的属性类型,而不用断言了。

完整代码

组件定义

html 复制代码
<template>
  <div class="list">
    <div
      v-for="item in items"
      :key="item.id"
      class="card"
      :class="item.disabled ? 'disabled' : ''"
      @click="item.disabled ? {} : emits('click', item)"
    >
      <div class="card-body">
        <div class="header">
          <div class="id">
            ID: {{ item.id }}
          </div>
          <div class="title">
            <slot
              name="title"
              :row="item"
            />
          </div>
        </div>
        <div class="description">
          {{ item.description || '暂无描述' }}
        </div>
      </div>
    </div>
  </div>
</template>

<script lang="ts" setup generic="T extends Item">
import { Item } from './Item'

defineProps({
  items: {
    type: Array<T>,
    required: true,
  }
})

const emits = defineEmits<{
  click:[T]
}>()
</script>

<style lang="scss" scoped>
.list {
  display: flex;
  flex-wrap: wrap;
  flex-direction: column;
  gap: 1rem;
}
/* 一些其他样式 */

.disabled {
  background-color: #aaa;
  opacity: 0.1;
  pointer-events: none;
  
}
</style>

组件调用

html 复制代码
<template>
  <div class="home">
    <CardList
      :items="users"
      @click="console.log($event.name)"
    >
      <template #title="{ row }">
        {{ row.name }}
      </template>
    </CardList>
  </div>
</template>
<script lang="ts" setup>
import { Ref, ref } from 'vue'
import CardList from './console/card-list.vue'
import { User } from './console/User'

const users: Ref<User[]> = ref([])

for (let i = 1; i <= 5; i += 1) {
  let descrption = ''
  if (i % 3 === 0) {
    descrption = `第${i}个用户`
  }
  users.value.push({
    id: i,
    name: `name ${i}`,
    disabled: i % 2 === 0,
    age: i,
    description: descrption,
  })
}
</script>

实现的效果

总结

通过使用泛型组件,我们可以在定义组件时指定其类型参数,并在组件内部使用这些类型参数来定义组件的属性、事件和插槽。这样,我们就可以确保组件的参数类型和返回类型都是正确的,从而提高日常编码中的一些数据类型提示、类型检查等。

今天的话题就到这,拜拜了您嘞~

更多的源代码参考,可以关注我们的开源项目:

github.com/HammCn/AirP...

相关推荐
customer0821 分钟前
【开源免费】基于SpringBoot+Vue.JS个人博客系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
乐多_L37 分钟前
使用vue3框架vue-next-admin导出表格excel(带图片)
前端·javascript·vue.js
初尘屿风1 小时前
基于微信小程序的电影院订票选座系统的设计与实现,SSM+Vue+毕业论文+开题报告+任务书+指导搭建视频
vue.js·微信小程序·小程序
南望无一1 小时前
React Native 0.70.x如何从本地安卓源码(ReactAndroid)构建
前端·react native
Mike_188702783511 小时前
1688代采下单API接口使用指南:实现商品采集与自动化下单
前端·python·自动化
customer081 小时前
【开源免费】基于SpringBoot+Vue.JS打卡健康评测系统(JAVA毕业设计)
java·vue.js·spring boot·后端·开源
鲨鱼辣椒️面1 小时前
HTML视口动画
前端·html
一小路一1 小时前
Go Web 开发基础:从入门到实战
服务器·前端·后端·面试·golang
堇舟1 小时前
HTML第一节
前端·html
纯粹要努力1 小时前
前端跨域问题及解决方案
前端·javascript·面试