作者:TinssonTai
https://juejin.im/post/6875713523968802829
一個完整的Vue3+Ts項目,支持.vue和.tsx寫法
項目地址:https://github.com/vincentzyc/vue3-demo.git
TypeScript
是JS的一個超集,主要提供了類型系統和對ES6的支持,使用 TypeScript
可以增加代碼的可讀性和可維護性,在 react
和 vue
社區中也越來越多人開始使用TypeScript
。從最近發布的 Vue3
正式版本來看, Vue3
的源碼就是用 TypeScript
編寫的,更好的 TypeScript
支持也是這一次升級的亮點。當然,在實際開發中如何正確擁抱 TypeScript
也是遷移至 Vue3
的一個小痛點,這里就針對 Vue3
和 TypeScript
展開一些交流。

96.8%的代碼都是TypeScript,支持的力度也是相當大?
- Vue3入口: https://github.com/vuejs/vue-next
項目搭建
在官方倉庫的 Quickstart 中推薦用兩種方式方式來構建我們的 SPA
項目:
- vite
npm?init?vite-app?sail-vue3?#?OR?yarn?create?vite-app?sail-vue3
- vue-cli
npm?install?-g?@vue/cli?#?OR?yarn?global?add?@vue/cli
vue?create?sail-vue3
#?select?vue?3?preset
vite
是一個由原生ESM
驅動的Web開發構建工具,打開 vite
依賴的 package.json
可以發現在 devDependencies
開發依賴里面已經引入了TypeScript
,甚至還有 vuex
, vue-router
, less
, sass
這些本地開發經常需要用到的工具。vite
輕量,開箱即用的特點,滿足了大部分開發場景的需求,作為快速啟動本地 Vue
項目來說,這是一個非常完美的工具。
后面的演示代碼也是用vite搭的
從 vue2.x
走過來的掘友肯定知道 vue-cli
這個官方腳手架, vue3
的更新怎么能少得了 vue-cli
呢, vue-cli
更強調的是用 cli
的方式進行交互式的配置,選擇起來更加靈活可控。豐富的官方插件適配,GUI的創建管理界面,標準化開發流程,這些都是 vue-cli
的特點。
- vue-cli ? TypeScript STEP1

- vue-cli ? TypeScript STEP2

想要預裝TypeScript,就需要選擇手動配置,并check好TypeScript
忘記使用選擇 TypeScript
也沒事,加一行cli命令就行了
vue?add?typescript
最后,別忘了在 .vue
代碼中,給 script
標簽加上 lang="ts"
<script?lang="ts">
Option API風格
在 Vue2.x
使用過 TypeScript
的掘友肯定知道引入 TypeScript
不是一件簡單的事情:
- 要用
vue-class-component
強化vue
組件,讓Script
支持TypeScript
裝飾器 - 用
vue-property-decorator
來增加更多結合Vue
特性的裝飾器 - 引入
ts-loader
讓webpack
識別.ts
.tsx
文件 - .....
然后出來的代碼風格是這樣的:
@Component({
????components:{?componentA,?componentB},
})
export?default?class?Parent?extends?Vue{
??@Prop(Number)?readonly?propA!:?number?|?undefined
??@Prop({?default:?'default?value'?})?readonly?propB!:?string
??@Prop([String,?Boolean])?readonly?propC!:?string?|?boolean?|?undefined
??//?data信息
??message?=?'Vue2?code?style'
??//?計算屬性
??private?get?reversedMessage?():?string[]?{
??????return?this.message.split('?').reverse().join('')
??}
??//?method
??public?changeMessage?():?void?{
????this.message?=?'Good?bye'
??}
}
class
風格的組件,各種裝飾器穿插在代碼中,有點感覺自己不是在寫 vue
,些許凌亂?,所以這種曲線救國的方案在 vue3
里面肯定是行不通的。
在 vue3
中可以直接這么寫:
import?{?defineComponent,?PropType?}?from?'vue'
interface?Student?{
??name:?string
??class:?stringage:?number
}const?Component?=?defineComponent({
??props:?{
????success:?{?type:?String?},
????callback:?{
??????type:?Function?as?PropType<()?=>?void>
????},
????student:?{
??????type:?Object?as?PropType,required:?true
????}
??},
??data()?{return?{message:?'Vue3?code?style'
????}
??},computed:?{
????reversedMessage():?string?{return?this.message.split('?').reverse().join('')
????}
??}
})
vue
對 props
進行復雜類型驗證的時候,就直接用 PropType
進行強制轉換, data
中返回的數據也能在不顯式定義類型的時候推斷出大多類型, computed
也只用返回類型的計算屬性即可,代碼清晰,邏輯簡單,同時也保證了 vue
結構的完整性。
Composition API風格
在 vue3
的 Composition API
代碼風格中,比較有代表性的api就是 ref
和 reactive
,我們看看這兩個是如何做類型聲明的:
ref
import?{?defineComponent,?ref?}?from?'vue'
const?Component?=?defineComponent({
setup()?{
??const?year?=?ref(2020)
??const?month?=?ref('9')
??month.value?=?9?//?OKconst?result?=?year.value.split('')?//?=>?Property?'split'?does?not?exist?on?type?'number'
?}
})
分析上面的代碼,可以發現如果我們不給定 ref
定義的類型的話, vue3
也能根據初始值來進行類型推導,然后需要指定復雜類型的時候簡單傳遞一個泛型即可。
Tips:如果只有setup方法的話,可以直接在defineComponent中傳入setup函數
const?Component?=?defineComponent(()?=>?{
????const?year?=?ref(2020)
????const?month?=?ref('9')
????month.value?=?9?//?OKconst?result?=?year.value.split('')?//?=>?Property?'split'?does?not?exist?on?type?'number'
})
reactive
import?{?defineComponent,?reactive?}?from?'vue'
interface?Student?{
??name:?string
??class?:?stringage:?number
}export?default?defineComponent({
??name:?'HelloWorld',
??setup()?{
????const?student?=?reactive({?name:?'阿勇',?age:?16?})//?orconst?student:?Student?=?reactive({?name:?'阿勇',?age:?16?})//?orconst?student?=?reactive({?name:?'阿勇',?age:?16,?class:?'cs'?})?as?Student
??}
})
聲明 reactive
的時候就很推薦使用接口了,然后怎么使用類型斷言就有很多種選擇了,這是 TypeScript
的語法糖,本質上都是一樣的。
自定義Hooks
vue3
借鑒 react hooks
開發出了 Composition API
,那么也就意味著 Composition API
也能進行自定義封裝 hooks
,接下來我們就用 TypeScript
風格封裝一個計數器邏輯的 hooks
( useCount
):
首先來看看這個 hooks
怎么使用:
import?{?ref?}?from?'/@modules/vue'
import??useCount?from?'./useCount'
export?default?{
??name:?'CountDemo',
??props:?{
????msg:?String
??},
??setup()?{
????const?{?current:?count,?inc,?dec,?set,?reset?}?=?useCount(2,?{
??????min:?1,
??????max:?15
????})
????const?msg?=?ref('Demo?useCount')
????return?{
??????count,
??????inc,
??????dec,
??????set,
??????reset,
??????msg
????}
??}
}
出來的效果就是:

貼上 useCount
的源碼:
import?{?ref,?Ref,?watch?}?from?'vue'
interface?Range?{
??min?:?number,
??max?:?number
}
interface?Result?{
??current:?Ref,inc:?(delta?:?number)?=>?void,dec:?(delta?:?number)?=>?void,set:?(value:?number)?=>?void,reset:?()?=>?void
}export?default?function?useCount(initialVal:?number,?range?:?Range):?Result?{const?current?=?ref(initialVal)const?inc?=?(delta?:?number):?void?=>?{if?(typeof?delta?===?'number')?{
??????current.value?+=?delta
????}?else?{
??????current.value?+=?1
????}
??}const?dec?=?(delta?:?number):?void?=>?{if?(typeof?delta?===?'number')?{
??????current.value?-=?delta
????}?else?{
??????current.value?-=?1
????}
??}const?set?=?(value:?number):?void?=>?{
????current.value?=?value
??}const?reset?=?()?=>?{
????current.value?=?initialVal
??}
??watch(current,?(newVal:?number,?oldVal:?number)?=>?{if?(newVal?===?oldVal)?returnif?(range?&&?range.min?&&?newVal???????current.value?=?range.min
????}?else?if?(range?&&?range.max?&&?newVal?>?range.max)?{
??????current.value?=?range.max
????}
??})return?{
????current,
????inc,
????dec,set,
????reset
??}
}
分析源碼
這里首先是對 hooks
函數的入參類型和返回類型進行了定義,入參的 Range
和返回的 Result
分別用一個接口來指定,這樣做了以后,最大的好處就是在使用 useCount
函數的時候,ide就會自動提示哪些參數是必填項,各個參數的類型是什么,防止業務邏輯出錯。

接下來,在增加 inc
和減少 dec
的兩個函數中增加了 typeo
類型守衛檢查,因為傳入的 delta
類型值在某些特定場景下不是很確定,比如在 template
中調用方法的話,類型檢查可能會失效,傳入的類型就是一個原生的 Event
。
關于 ref
類型值,這里并沒有特別聲明類型,因為 vue3
會進行自動類型推導,但如果是復雜類型的話可以采用類型斷言的方式:ref(initObj) as Ref
小建議 ?
AnyScript
在初期使用 TypeScript
的時候,很多掘友都很喜歡使用 any
類型,硬生生把TypeScript
寫成了 AnyScript
,雖然使用起來很方便,但是這就失去了 TypeScript
的類型檢查意義了,當然寫類型的習慣是需要慢慢去養成的,不用急于一時。
Vetur
vetur
代碼檢查工具在寫vue代碼的時候會非常有用,就像構建 vue
項目少不了 vue-cli
一樣,vetur
提供了 vscode
的插件支持,趕著升級 vue3
這一波工作,順帶也把 vetur
也帶上吧。

一個完整的Vue3+ts項目
├─public
│??????favicon.ico
│??????index.html
└─src
????│??App.vue
????│??main.ts
????│??shims-vue.d.ts
????├─assets
????│??│??logo.png
????│??└─css
????│??????????base.css
????│??????????main.styl
????├─components
????│??│??HelloWorld.vue
????│??└─base
????│??????????Button.vue
????│??????????index.ts
????│??????????Select.vue
????├─directive
????│??????focus.ts
????│??????index.ts
????│??????pin.ts
????├─router
????│??????index.ts
????├─store
????│??????index.ts
????├─utils
????│??│??cookie.ts
????│??│??deep-clone.ts
????│??│??index.ts
????│??│??storage.ts
????│??└─validate
????│??????????date.ts
????│??????????email.ts
????│??????????mobile.ts
????│??????????number.ts
????│??????????system.ts
????└─views
????????│??About.vue
????????│??Home.vue
????????│??LuckDraw.vue
????????│??TodoList.vue
????????└─address
????????????????AddressEdit.tsx
????????????????AddressList.tsx
- .vue寫法
??...
???...
- tsx寫法
import?{?ref,?reactive?}?from?"vue";
import?{?AddressList,?NavBar,?Toast,?Popup?}?from?"vant";
import?AddressEdit?from?'./AddressEdit'
import?router?from?'@/router'
export?default?{
??setup()?{
????const?chosenAddressId?=?ref('1')
????const?showEdit?=?ref(false)
????const?list?=?reactive([
??????{
????????id:?'1',
????????name:?'張三',
????????tel:?'13000000000',
????????address:?'浙江省杭州市西湖區文三路?138?號東方通信大廈?7?樓?501?室',
????????isDefault:?true,
??????},
??????{
????????id:?'2',
????????name:?'李四',
????????tel:?'1310000000',
????????address:?'浙江省杭州市拱墅區莫干山路?50?號',
??????},
????])
????const?disabledList?=?reactive([
??????{
????????id:?'3',
????????name:?'王五',
????????tel:?'1320000000',
????????address:?'浙江省杭州市濱江區江南大道?15?號',
??????},
????])
????const?onAdd?=?()?=>?{
??????showEdit.value?=?true
????}
????const?onEdit?=?(item:?any,?index:?string)?=>?{
??????Toast('編輯地址:'?+?index);
????}
????const?onClickLeft?=?()?=>?{
??????router.back()
????}
????const?onClickRight?=?()?=>?{
??????router.push('/todoList')
????}
????return?()?=>?{
??????return?(
????????
????????????
????????????left-text="返回"
????????????right-text="Todo"
????????????left-arrow
????????????onClick-left={onClickLeft}
????????????onClick-right={onClickRight}
??????????/>????????????vModel={chosenAddressId.value}
????????????list={list}
????????????disabledList={disabledList}
????????????disabledText="以下地址超出配送范圍"
????????????defaultTagText="默認"
????????????onAdd={onAdd}
????????????onEdit={onEdit}
??????????/>
??????);
????};
??}
};
結束
不知不覺, Vue
都到3的One Piece時代了, Vue3
的新特性讓擁抱 TypeScript
的姿勢更加從容優雅, Vue
面向大型項目開發也更加有底氣了,點擊查看更多。
點個『在看』支持下?