Skip to main content

Vue2的 Option-Based Component中使用Typescript的簡易方式

· 8 min read
Adam You

前言

之前使用 Vue2.x開發時又想使用 Typescript,當時遇到的困擾是… Component使用 Typescript時根本就吃不到 this的型別,所以基本上是無法直接使用的,唯一的方式也只能採用官方推薦的 Class Component寫法,會寫到覺得很生氣(?), Class Component的寫法實在是頗不直觀。

一直到 Vue3.0推出之後,才終於徹底改善了對 Typescript的支援;但我的工作上基於某些理由,短時間內無法轉為使用 Vue3.0,所以我就來簡單研究了一下,在 Vue2.x環境下是否有使用 Typescript合適方式?

使用 Composition Api

都說要使用 Vue2.x的 Option-Based Component了,何來 Composition Api?但本篇文章要提出來的解決方式正是 Composition Api。

雖然 Composition Api是 Vue3.0的產物,但官方也提供了 Composition Api for Vue2.x的套件,在 Vue2.x的環境中安裝之後就可以使用 Composition Api。

然後重點來了,使用 Composition Api仍然是相容 Option的寫法了,所以你可以不改變原本寫法的情況下,得到差不多於 Vue3.x對於 Typescript的支援度(應該是吧)。最簡的使用範例如下:

import { defineComponent } from '@vue/composition-api'

export default defineComponent({
// 可維持原本 Option的寫法
props: {
},
data () {
return {}
},
// 略…
})

然而在我的實測中發現會有些小細節是和 Vue3.0環境中會有些差異,這個在後面會再做說明。

Props的型別

Vue 原生就有定義 Props型別的功能(type欄位),但是 type欄位只限於 javascript的原生型別,如果想要宣告為 Typescript的型別的話有以下兩種方式:

方法1 - 使用 PropType

import { defineComponent, PropType } from '@vue/composition-api'

type Value {
label: string;
value: string;
}

export default defineComponent({
props: {
value: {
type: Object as PropType<Value>
}
}
})

方法2 - 使用 function return Type/Interface:

import { defineComponent, PropType } from '@vue/composition-api'

type MyValue = {
label: string;
value: string;
}

export default defineComponent({
props: {
value: {
type: Object as () => MyValue
// 注意!以下寫法是錯的喔!
// type: Object as Value
}
}
})

Option上「data」欄位的型別

Component的 data欄位本身就是一個 function,所以在宣告型別上也是很簡單的,有以下兩種方式:

方法1 - 使用註記 (Annotation)的方式

import { defineComponent } from '@vue/composition-api'

type MyValue = {
label: string;
value: string;
}

export default defineComponent({
data () {
const value: MyValue = {
label: 'default',
value: 'default'
}
return {
value
}
}
})

方法1 - 使用推論 (Inference)的方式

import { defineComponent } from '@vue/composition-api'

type MyValue = {
label: string;
value: string;
}

export default defineComponent({
data () {
return {
value: {
label: 'default',
value: 'default'
} as MyValue
}
}
})

Vue2.x Composition Api和 Vue3.0的部份差異

既然都使用 Composition Api了,自然也不會想死守著舊的 Option的寫法,想嘗試看看 Composition Api的威力。

不過我實際在使用的時候,還是踩了一些坑,發現由於 Vue2.x和 Vue3.0在底層上有一些基本的差異,所以寫法並不會 100%一模一樣的;以下是我目前為止遇到的問題:

1. 使用 set新增物件欄位

從 Vue2.x開始學起的開發者應該都知道 $set的寫法以及原理,這個是一個蠻大的主題所以在這裡就不深入去探討了(要解釋清楚需要一整篇文章來解釋,網路上也有相當多的文章了)。

簡單講呢… Vue3.0 的底層改使用 proxy取代 Vue2.x的 definedObject來實做資料的雙向綁定,所以 Vue3.0的 Component中 data中宣告物件時,物件要新增欄位不需要使用 $set也能監聽到資料的異動。

但 Vue2.x即使是裝了 Composition Api,仍然受到底層的限制,寫法上自然和 Vue3.0有些差異。

在 Composition Api的 setup當中由於不能用 this來使用 Vue實例上的 $set,所以必須額外 import Composition Api提供的 set。範例如下:

import { defineComponent, reactive, set, SetupContext, Data } from '@vue/composition-api'

export default defineComponent({
setup (props: Data, context: SetupContext) {

const data: any = reactive({
selectedValueArr: []
})

const selectDropdown = (key: string, item: { label: string; value: any; }) => {
const index = props.dropdowns
.map(d => d.name)
.indexOf(key)
// 使用set加入新的欄位資料
set(data.selectedValueArr, index, item.value)
// 以下寫法無法雙向綁定資料
// data.selectedValueArr[index] = item.value
}

return {
selectDropdown
}
}
})

2. 某些能 import進來的東西「可能」不一樣

以我有限的使用經驗,還不曉得所有可能的差異,就我目前所遇到的是 Vue2.x Composition Api有一個叫做Data的 type,在 Vue3.0中是沒有的,依照我找到的資料說明Data是用在setup中注入props所使用預設型別。

範例如下:

// vue2.x composition-api
import { defineComponent, SetupContext, Data } from '@vue/composition-api'
// vue3.0 以下寫法會找不到 Data 所以會出錯
// import { defineComponent, SetupContext, Data } from 'vue'

export default defineComponent({
props: ['value'],
setup (props: Data, context: SetupContext) {

}
})

不過呢…

Data這個東西我感覺他沒有什麼實用性,檢視他的型別其實是這樣子。

type Data = {
[key: string]: unknown;
}

再來,如果你有按照前一個章節所介紹的方式去註記props的型別,在setup中所注入的props是完全不需要再註記的,Typescript他自己就可以推論出來。

所以這裡我唯一要解釋的,只是說@vue/composition-api和 Vue3.0能 import可能會有所不同,這裡僅是作為範例來說明。

至於是否有其他實際影響使用的差異,因為的經驗有限所以還不曉得了(我很抱歉 XD)。

結論

既然 Class Component在 Vue3.0之後已經被官方所棄用,所以我也覺得沒有任何理由要再採用 Class Component。

本篇文章我依照我自己在工作上的經驗所整理出來的,能夠在 Vue2.x的環境下也能方便的結合 Typescript,希望對大家有幫助。