08Vue+TS實(shí)戰(zhàn)
準(zhǔn)備工作
新建一個(gè)基于 TS 的 Vue 項(xiàng)目:
在已存在項(xiàng)目中安裝 TS
vue add @vue/typescript
請(qǐng)暫時(shí)忽略引發(fā)的幾處 Error垦写,它們不影響項(xiàng)目運(yùn)行吕世,我們將在后面處理它們。
TS 特點(diǎn)
TypeScript 是 JavaScript 的超集梯投,它可編譯為純 JavaScript命辖,是一種給 JavaScript 添加特性的語(yǔ)言擴(kuò)展况毅。TS 有如下特點(diǎn):
類型注解和編譯時(shí)類型檢查
基于類的面向?qū)ο缶幊?/p>
泛型
接口
裝飾器
類型聲明
類型注解和編譯時(shí)類型檢查
使用類型注解約束變量類型,編譯器可以做靜態(tài)類型檢查尔艇,使程序更加健壯尔许。
基礎(chǔ)類型
// ts-test.ts
let var1: string // 類型注解
var1 = "林慕" // 正確
var1 = 4 // 錯(cuò)誤
編譯器類型推斷可省略這個(gè)語(yǔ)法
let var2 = true
常見的原始類型:
string
number
boolean
undefined
null
symbol
類型數(shù)組
let arr: string[]
arr = ['林慕'] // 或 Array<string>
任意類型 any
let varAny: any
varAny = 'xx'
varAny = 3
任意類型也可用于數(shù)組
let arrAny: any[]
arrAny = [1,true,'free']
arrAny [1] = 100
函數(shù)中的類型約束
function greet(person: string): string{
return 'hello,'+person
}
void 類型,常用于沒有返回值的函數(shù)
function warn(): void{}
范例:HelloWorld.vue
<template>
<div>
<ul>
<li v-for="feature in features" :key="feature">{{feature}}</li>
</ul>
</div>
</template>
<script lang='ts'>
import { Component, Prop, Vue } from "vue-property-decorator";
@Component
export default class Hello extends Vue {
features: string[] = ["類型注解", "編譯型語(yǔ)言"];
}
</script>
類型別名
使用類型別名自定義類型终娃。
可以用下面這種方式定義對(duì)象類型:
const objType: {foo:string,bar:string}
使用 type 定義類型別名味廊,使用更便捷,還能復(fù)用:
type Foobar = { foo: string, bar: string }
const aliasType: Foobar
范例:使用類型別名定義 Feature棠耕,types/index.ts
export type Feature = {
id: number,
name: string
}
使用自定義類型余佛,HelloWorld.vue
<template>
<div>
<!--修改模板-->
<li v-for="feature in features" :key="feature.id">{{feature.name}}</li>
</div>
</template>
<script lang='ts'>
// 導(dǎo)入接口
import { Feature } from '@/types'
@Component
export default class Hello extends Vue {
// 修改數(shù)據(jù)類型
features: Feature[] = [{ id: 1, name: '類型注解' }]
}
</script>
聯(lián)合類型
希望某個(gè)變量或參數(shù)的類型是多種類型其中之一。
let union: string | number
union = '1'
union = 1
// 以上都是可以的
交叉類型
想要定義某種由多種類型合并而成的類型使用交叉類型窍荧。
type First = {first: number}
type Second = {second: number}
// fas將同時(shí)擁有屬性first和second
type fas = First & Second
范例:利用交叉類型給 Feature 添加一個(gè) selected 屬性辉巡。
// types/index.ts
type Select = {
selected: boolean
}
export type FeatureSelect = Feature & Select
使用這個(gè) FeatureSelect,HelloWorld.vue
features: FeatureSelect[] = [
{ id: 1, name: '類型注解', selected: false },
{ id: 2, name: '編譯型語(yǔ)言', selected: true }
]
<li :class="{selected: feature.selected}">{{feature.name}}</li>
.selected {
background-color: rgb(168, 212, 247)
}
函數(shù)
必填參:參數(shù)一旦聲明蕊退,就要求傳遞郊楣,且類型需符合。
// 02-function.ts
function greeting(person: string): string {
return 'hello,' + person
}
greeting('tom')
可選參數(shù):參數(shù)名后面加上問(wèn)號(hào)瓤荔,變成可選參數(shù)净蚤。
function greeting(person: string, msg?: string):string {
return 'hello,' + person
}
默認(rèn)值:
function greeting(person: string, msg = ''): string{
return 'hello,' + person
}
函數(shù)重載:以參數(shù)數(shù)量或類型區(qū)分多個(gè)同名函數(shù)。
// 重載1
function watch(cb: () => void): void
// 重載2
function watch(cb1: () =>void,cb2:(v1:any, v2:any) => void): void
// 實(shí)現(xiàn)
function watch(cb1: () => void, cb2?: (v1: any, v2: any) => void){
if(cb1 && cb2){
console.log('執(zhí)行watch重載2')
} else {
console.log('執(zhí)行watch重載1')
}
}
范例:新增特性茉贡,Hello.vue
<div>
<input type="text" placeholder="輸入新特性" @keyup.enter="addFeature">
</div>
addFeature(e: KeyboardEvent) {
// e.target 是 EventTarget 類型塞栅,需要斷言為 HTMLInputElement
const inp = e.target as HTMLInputElement
const feature: FeatureSelect = {
id: this.features.length + 1,
name: inp.value,
selected: false
}
this.features.push(feature)
inp.value = ''
}
范例:生命周期鉤子,Hello.vue
created() {
this.features = [{ id:1, name: '類型注解' }]
}
類
class 的特性
TS 中的類和 ES6 中大體相同腔丧,這里重點(diǎn)關(guān)注 TS 帶來(lái)的訪問(wèn)控制等特性放椰。
// 03-class.ts
class Parent {
private _foo = 'foo' // 私有屬性,不能在類的外部訪問(wèn)
protected bar = 'bar' // 保護(hù)屬性愉粤,可以在子類中使用
// 參數(shù)屬性:構(gòu)造函數(shù)參數(shù)加修飾符砾医,能夠定義為成員屬性
constructor(public tua = 'tua') {}
// 方法也有修飾符
private someMethod() {}
// 存取器:屬性方式訪問(wèn),可添加額外邏輯衣厘,控制讀寫性
get foo() {
return this._foo
}
set foo(val) {
this._foo = val
}
}
范例:利用 getter 設(shè)置計(jì)算屬性如蚜,Hello.vue
<template>
<li>特性數(shù)量:{{count}}</li>
</template>
<script lang='ts'>
export default class HelloWorld extends Vue {
// 定義getter作為計(jì)算屬性
get count() {
return this.features.length
}
}
</script>
接口
接口僅約束結(jié)構(gòu),不要求實(shí)現(xiàn)影暴,使用更簡(jiǎn)單
// 04-interface
// Person接口定義了結(jié)構(gòu)
interface Person {
firstName: string;
lastName: string;
}
// greeting函數(shù)通過(guò)Person接口約束參數(shù)解構(gòu)
function greeting(person: Person) {
return 'Hello,' + person.firstName + ' ' + person.lastName
}
greeting({firstName: 'Jane', lastName: 'User'}) // 正確
greeting({firstName: 'Jane'}) // 錯(cuò)誤
范例:Feature 也可用接口形式約束错邦,./types/index.ts
接口中只需定義結(jié)構(gòu),不需要初始化
export interface Feature {
id: number;
name: string;
}
泛型
泛型(Generics)是指在定義函數(shù)型宙、接口或類的時(shí)候撬呢,不預(yù)先指定具體的類型,而在使用的時(shí)候再指定類型的一種特性妆兑。以此增加代碼通用性魂拦。
不使用泛型
interface Result {
ok: 0 | 1;
data: Feature[];
}
使用泛型
interface Result<T> {
ok: 0 | 1;
data: T;
}
泛型方法
function getResult<T>(data: T): Result<T> {
return {ok:1, data}
}
// 用尖括號(hào)方式指定T為string
getResult<string>('hello')
// 用類型推斷指定T為number
getResult(1)
泛型優(yōu)點(diǎn):
函數(shù)和類可以支持多種類型毛仪,更加通用;
不必編寫多條重載芯勘,冗長(zhǎng)聯(lián)合類型箱靴,可讀性好;
靈活控制類型約束荷愕;
不僅通用且能靈活控制衡怀,泛型被廣泛用于通用庫(kù)的編寫。
范例:用 axios 獲取數(shù)據(jù)
配置模擬一個(gè)接口路翻,vue.config.js
module.exports = {
devServer: {
before(app) {
app.get('/api/list', (req, res) => {
res.json([
{ id: 1, name: "類型注解", version: "2.0" },
{ id: 2, name: "編譯型語(yǔ)言", version: "1.0" }
])
})
}
}
}
使用接口狈癞,HelloWorld.vue
async mounted () {
const resp = await axios.get<FeatureSelect[]>('/api/list')
this.features = resp.data
}
聲明文件
使用 TS 開發(fā)時(shí)如果要使用第三方 JS 庫(kù)的同時(shí)還想利用 TS 諸如類型檢查等特性就需要聲明文件茄靠,類似 xx.d.ts茂契。
同時(shí),Vue 項(xiàng)目中還可以在 shims-vue.d.ts 中對(duì)已存在模塊進(jìn)行補(bǔ)充慨绳。
npm i @types/xxx
范例:利用模塊補(bǔ)充 $axios 屬性到 Vue 實(shí)例掉冶,從而在組件里面直接用。
// main.ts
import axios from 'axios'
Vue.prototype.$axios = axios
// shims-vue.d.ts
import Vue from 'vue'
import { AxiosInstance } from 'axios'
declare module 'vue/types/vue' {
interface Vue {
$axios: AxiosInstance;
}
}
范例:給 router/index.js 編寫聲明文件脐雪,index.d.ts
import VueRouter from 'vue-router'
declare const router: VueRouter
export default router
裝飾器
裝飾器用于擴(kuò)展類或者它的屬性和方法厌小,@xxx 就是裝飾器的寫法。
屬性聲明:@Prop
除了在 @Component 中聲明战秋,還可以采用 @Prop 的方式聲明組件屬性璧亚。
export default class HelloWorld extends Vue {
// Props() 參數(shù)是為 Vue 提供屬性選項(xiàng),加括號(hào)說(shuō)明 prop 是一個(gè)裝飾器工廠脂信,返回的才是裝飾器癣蟋,參數(shù)一般是配置對(duì)象
// !稱為明確賦值斷言狰闪,它是提供給 TS 的
@Prop({type: String, required: true})
private msg!: string // 這行約束是寫給 TS 編譯器的
}
事件處理:@Emit
新增特性時(shí)派發(fā)事件通知疯搅,Hello.vue
// 通知父類新增事件,若未指定事件名則函數(shù)名作為事件名(羊肉串形式)
@Emit()
private addFeature(event: any){ // 若沒有返回值形參將作為事件參數(shù)
const feature = { name: event.target.value, id: this.features.length + 1}
this.features.push(feature)
event.target.value = ''
return feature // 若有返回值則返回值作為事件參數(shù)
}
變更監(jiān)測(cè):@Watch
@Watch('msg')
onMsgChange(val:string, oldVal:any){
console.log(val,oldVal)
}
狀態(tài)管理推薦使用:vuex-module-decorators
vuex-module-decorators 通過(guò)裝飾器提供模塊化聲明 Vuex 模塊的方法埋泵,可以有效利用 TS 的類型系統(tǒng)幔欧。
安裝
npm i vuex-modulw-decorators -D
根模塊清空,修改 store/index.ts
export default new Vuex.Store({})
定義 counter 模塊丽声,創(chuàng)建 store/counter
import { Module, VuexModule, Mutation, Action, getModule } from 'vuex-module-
decorators'
import store from './index'
// 動(dòng)態(tài)注冊(cè)模塊
@Module({ dynamic: true, store: store, name: 'counter', namespaced: true })
class CounterModule extends VuexModule {
count = 1
@Mutation
add () {
// 通過(guò)this直接訪問(wèn)count
this.count++
}
// 定義getters
get doubleCount () {
return this.count * 2;
}
@Action
asyncAdd () {
setTimeout(() => {
// 通過(guò)this直接訪問(wèn)add
this.add()
}, 1000);
}
}
// 導(dǎo)出模塊應(yīng)該是getModule的結(jié)果
export default getModule(CounterModule)
使用礁蔗,App.vue
<p @click="add">{{$store.state.counter.count}}</p>
<p @click="asyncAdd">{{count}}</p>
import CounterModule from '@/store/counter'
@Component
export default class App extends Vue {
get count() {
return CounterModule.count
}
add() {
CounterModule.add()
}
asyncAdd() {
CounterModule.asyncAdd()
}
}
裝飾器原理
裝飾器是加工廠函數(shù),他能訪問(wèn)和修改裝飾目標(biāo)雁社。
類裝飾器:類裝飾器表達(dá)式會(huì)在運(yùn)行時(shí)當(dāng)作函數(shù)被調(diào)用浴井,類的構(gòu)造函數(shù)作為其唯一的參數(shù)。
function log(target: Function){
// target 是構(gòu)造函數(shù)
console.log(target === Foo) // true
target.prototype.log = function() {
console.log(this.bar)
}
}
@log
class Foo{
bar = 'bar'
}
const foo = new Foo()
// @ts-ignore
foo.log()
方法裝飾器
function rec (target: any, name: String, descriptor: any) {
// 這里通過(guò)修改descriptor.value擴(kuò)展了bar方法
const baz = descriptor.value
descriptor.value=function(val: string){
console.log('run method',name)
baz.call(this,val)
}
}
class Foo{
@rec
setBar(val:string){
this.bar = val
}
}
foo.setBar('lalala')
屬性裝飾器
function mua(target,name){
target[name]='mua~'
}
class Foo{
@mua ns!:string
}
console.log(foo.ns)
稍微改造一下使其可以接收參數(shù):
function mua(params:string){
return function (target, name){
target[name] = param
}
}
實(shí)戰(zhàn)一下:
<template>
<div>{{ msg }}</div>
</template>
<script lang='ts'>
import { Vue } from "vue-property-decorator";
function Component(options: any) {
return function(target: any) {
return Vue.extend(options);
};
}
@Component({
props: {
msg: {
type: String,
default: ""
}
}
})
export default class Decor extends Vue {}
</script>
顯然 options 中的選項(xiàng)都可以從 Decor 定義中找到歧胁。
參考資料
TypeScript:https://www.typescriptlang.org/
TypeScript 支持 Vue:https://cn.vuejs.org/v2/guide/typescript.html