Vue 泛型元件,讓 TypeScript 更精確

vue-generic-components.jpg

大家好,我是鱈魚。(^∀^●)ノシ

Vue 3.3 終於新增了泛型元件(Generic Component),可以在 TypeScript 環境中得到更準確的型別提示了!ˋ( ° ▽、° )

這個泛型元件與 TypeScript 的 Generic 是同一個概念,不知道 Generic 的朋友可以先來來複習一下

甚麼?你說你沒有使用 TypeScript?抱歉浪費您 5 秒鐘,可以上一頁了。(>人<;)

或者在離開前路過文件湊個熱鬧。( •̀ ω •́ )y

有使用 TypeScript 的朋友就讓我們一起繼續看下去吧。ლ(╹◡╹ლ)

來個 select

假設我需要一個綁定物件的 select,最常見的問題就是 modelValue 只能 any,如下:

SelectAny.vue

<script setup lang="ts">
...(省略其他程式碼)

interface Prop {
  modelValue: any;
  label?: string;
  options: any[];
  optionLabel?: (option: any) => string;
  /** 是否多選 */
  multiple?: boolean;
}

...
</script>
...

v-model 這個糖

忘記元件怎麼實現 v-model 的朋友們別擔心,讓我們複習一下。

Vue 3.4 開始可以使用 defineModel,不過與此文主題無關,所以我們先使用經典寫法。

v-model 只是一個語法糖,實際上的程式碼如下(文件)。

<script setup>
const props = defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>

<template>
  <input
    :value="props.modelValue"
    @input="emit('update:modelValue', $event.target.value)"
  />
</template>

modelValue 就是用來表示預設綁定值的變數,讓我們回到剛剛的 any。

所以現在就算我們的 modelValue 與 options 的型別不同,或者 multiple 為 true,而 modelValue 卻沒有給矩陣時,都不會有任何錯誤提示。

<script setup lang="ts">
...
import SelectAny from './SelectAny.vue';

const options: User[] = [
  { name: '鱈魚', price: 10000 },
  { name: '章魚', price: 100 }
];

const anyDatum = ref<{ age: number }>();
const selectedAnyDatum = ref<User>();
</script>

<template>
 <!-- anyDatum 與 option 型別不同 -->
  <SelectAny v-model="anyDatum" :options="options" :option-label="getLabel" />
 
 <!-- modelValue 沒有給矩陣 -->
  <SelectAny v-model="selectedAnyDatum" multiple :options="options" :option-label="getLabel" />
</template>

畢竟是 any 嘛。(˘・_・˘)

加上泛型

如果加上元件泛型就不會有以上問題了。

SelectGeneric.vue

<script 
  setup 
  lang="ts" 
  generic="ModelValue, Multiple extends boolean"
>
...

interface Prop {
  modelValue: Multiple extends true ? ModelValue[] : ModelValue;
  label?: string;
  options: ModelValue[];
  optionLabel?: (option: any) => string;
  /** 是否多選 */
  multiple?: Multiple;
}

...
</script>
...

除了 modelValue、options 有泛型 ModelValue 外,我們還加上 multiple 判斷,如果為 true,則 modelValue 為 ModelValue,否則為 ModelValue。

這樣子就可以達成以下效果:

  • modelValue 與 options 型別不同時警告,
  • multiple 為 true 時,如果 modelValue 沒有提供矩陣時發出警告。
<script setup lang="ts">
...
import SelectGeneric from './SelectGeneric.vue';

const options: User[] = [
  { name: '鱈魚', price: 10000 },
  { name: '章魚', price: 100 }
];

const anyDatum = ref<{ age: number }>();
const selectedGenericDatum = ref<User>();
const selectedGenericData = ref<User[]>([]);
</script>

<template>
  <!-- Type '{ age: number; }' is not assignable to type 'User | User[]'.ts(2322) -->
  <SelectGeneric v-model="anyDatum" :options="options" :option-label="getLabel" />

 <!-- Type 'User' is not assignable to type 'User[]'.ts(2322) -->
  <SelectGeneric v-model="selectedGenericDatum" multiple :options="options" :option-label="getLabel" />
   
  <!-- 正確 -->
  <SelectGeneric v-model="selectedGenericData" multiple :options="options" :option-label="getLabel" />
</template>

TypeScript 成功重返光榮。o((>ω< ))o

文章到此結束了,感謝您的閱讀,以上程式碼可以在此取得。

有其他想法請不吝告訴我,鱈魚感謝您!(o゜▽゜)o☆

總結 🐟

  • Vue 3.3 新增泛型元件
  • 泛型元件可以讓 TypeScript 推導更精確