參考vue-typescript-admin-element-ui 基于Vue+typescript版的背景管理系統模闆。
衆所周知,js是一門弱類型的語言,尤其是在變量指派時,永遠都是給變量直接指派各種類型值來初始化,線上一些隐藏的bug就冷不防會暴露出來。把這種錯誤扼殺在項目開發編譯階段而非上線階段,所有就有了typescript超集的出現。那Vue中是怎麼引用typescript項目的呢
一、vue-property-decorator
vue-property-decorator在vue-class-component的基礎上增加了更多與Vue相關的裝飾器,使Vue元件更好的跟TS結合使用。這兩者都是離不開裝飾器的,(decorator)裝飾器已在ES提案中。Decorator是裝飾器模式的實踐。裝飾器模式呢,它是繼承關系的一個替代方案。動态地給對象添加額外的職責。在不改變接口的前提下,增強類的性能。
vue-property-decorator是這個Vue項目檔案中完全依賴的庫,它是Vue官方推薦的并且依賴于vue-class-component,先介紹下它在項目中的常見用法。
@Component
@Emit
@Provice @Inject
@Prop
@Watch
@Model
@Minxins
1、@Component 類裝飾器
首先,Vue頁面中的script部分要加一個lang=ts,這樣安裝好typescript正能引用
<script lang="ts">
import {Vue, Component} from 'vue-property-decorator';
import BaseHeader from '@/components/BaseHeader';
//公共頭部元件
@Component({
components: {
BaseHeader
}
})
export default class extends Vue {
private stateA:boolean = true
private stateB:string = ''
private stateC:number = 0
private stateD:any = {}
stateE:any[] = []
}
</script>
等同于
<script>
import Vue from 'vue';
import BaseHeader from '@/components/BaseHeader'; //公共頭部元件
export default {
components: {
BaseHeader
},
data(){
return {
stateA: true,
stateB: '',
stateC: 0,
stateD: {},
stateE: []
}
}
}
</script>
- vue-property-decorator在項目中的應用最主要是起一個裝飾器的作用,差異化的話看對比就非常直覺了
- data變量的定義比較多元化,這裡差別有加private,不加就是public,當變量标記為private時,它就不能在聲明它的類的外部通路。
- @Component裝飾器屬性名必須得寫上
2、@Prop
父子元件之間的屬性傳值
export default class extends Vue {
@Prop({ default: 0 }) private propA!: number
@Prop({ default: () => [10, 20, 30, 50] }) private propB!: number[]
@Prop({ default: 'total, sizes, prev, pager, next, jumper' }) private propC!: string
@Prop({ default: true }) private propD!: boolean,
@prop([String, Boolean]) propE: string | boolean;
}
等同于
export default {
props: {
propA: {
type: Number
},
propB: {
type: Array,
default: [10, 20, 30, 50]
},
propC: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
propD: {
type: String,
default: 'total, sizes, prev, pager, next, jumper'
},
propE: {
type: [String, Boolean]
}
}
}
這裡有兩個常用修飾符!``?,!和可選參數?是相對的, !表示強制解析(也就是告訴typescript編譯器,我這裡一定有值),你寫?的時候再調用,typescript會提示可能為undefined
3、@Emit
Component
export default class YourComponent extends Vue {
count = 0
@Emit('reset')
resetCount() {
this.count = 0
}
@Emit()
returnValue() {
return 10
}
@Emit()
onInputChange(e) {
return e.target.value
}
}
等同于
export default {
data() {
return {
count: 0
}
},
methods: {
resetCount() {
this.count = 0
this.$emit('reset')
},
returnValue() {
this.$emit('return-value', 10)
},
onInputChange(e) {
this.$emit('on-input-change', e.target.value, e)
}
}
}
@Emit裝飾器的函數會在運作之後觸發等同于其函數名(駝峰式會轉為橫杠式寫法)的事件, 并将其函數傳遞給$emit
@Emit觸發事件有兩種寫法
- @Emit()不傳參數,那麼它觸發的事件名就是它所修飾的函數名.
- @Emit(name: string),裡面傳遞一個字元串,該字元串為要觸發的事件名
4、@Watch 觀察屬性裝飾器
@Watch裝飾器主要用于替代Vue屬性中的watch屬性,監聽依賴的變量值變化而做一系列的操作
@Component
export default class YourComponent extends Vue {
@Watch('child')
onChildChanged(val: string, oldVal: string) {}
@Watch('person', { immediate: true, deep: true })
onPersonChanged(val: Person, oldVal: Person) {}
}
等同于
export default {
watch: {
child(val, oldVal) {},
person: {
handler(val, oldVal) {},
immediate: true,
deep: true
}
}
}
watch 是一個對象,對象就有鍵,有值。
- 第一個handler:其值是一個回調函數。即監聽到變化時應該執行的函數。
- 第二個是deep:其值是true或false;确認是否深入監聽。deep的意思就是深入觀察,監聽器會一層層的往下周遊,給對象的所有屬性都加上這個監聽器(受現代 JavaScript 的限制 (以及廢棄 Object.observe),Vue 不能檢測到對象屬性的添加或删除)
- 第三個是immediate:其值是true或false;immediate:true代表如果在 wacth 裡聲明了之後,就會立即先去執行裡面的handler方法,如果為 false就跟我們以前的效果一樣,不會在綁定的時候就執行
@Watch使用非常簡單,接受第一個參數為要監聽的屬性名, 第二個屬性為可選對象。@Watch所裝飾的函數即監聽到屬性變化之後應該執行的函數。
@Watch裝飾的函數的函數名并非如上onStateChanged嚴格命名,它是多元化的,你可以随心所欲的命名,當然,能按照規範化的命名會使你的代碼閱讀性更好。
5、@Minxins
// myMixin.ts
@Component
export default class MyMixin extends Vue {
mixinValue:string = 'Hello World!!!'
}
// 引用mixins
import MyMixin from './myMixin.js'
@Component
export default class extends mixins(MyMixin) {
created () {
console.log(this.mixinValue) // -> Hello World!!!
}
}
然後我又偷學到了另外一種mixins寫法,記錄一下
先改造一下myMixin.ts,定義vue/type/vue子產品,實作Vue接口
// myMixin.ts
import { Vue, Component } from 'vue-property-decorator';
declare module 'vue/types/vue' {
interface Vue {
mixinValue: string;
}
}
@Component
export default class myMixins extends Vue {
mixinValue: string = 'Hello World!!!'
}
引用
import { Vue, Component, Prop } from 'vue-property-decorator';
import MyMixin from './myMixin.js'
@Component({
mixins: [MyMixin]
})
export default class extends Vue{
created(){
console.log(mixinValue) // => Hello World!!!
}
}
兩種方式不同在于定義mixins時如果沒有定義vue/type/vue子產品, 那麼在混入的時候就要繼承該mixins; 如果定義vue/type/vue子產品,在混入時可以在@Component中mixins直接混入。
6、@Model
@Model裝飾器允許我們在一個元件上自定義v-model,接收兩個參數:
- event: string 事件名。
- options: Constructor | Constructor[] | PropOptions 與@Prop的第一個參數一緻。
import { Vue, Component, Model } from 'vue-property-decorator'
@Component
export default class MyInput extends Vue {
@Model('change', { type: String, default: 'Hello world!!!' }) readonly value!: string
}
等同于
<template>
<input
type="text"
:value="value"
@change="$emit('change', $event.target.value)"
/>
</template>
export default {
model: {
prop: 'value',
event: 'change'
},
props: {
value: {
type: String,
default: 'Hello world!!!'
}
}
}
7、@Provide @Inject
@Provide 聲明一個值 , 在其他地方用 @Inject 接收,在實戰項目中用得不多,一般用于不依賴于任何第三方狀态管理庫(如vuex)的元件編寫
8、@Ref(refKey?: string)
@Ref裝飾器接收一個可選參數,用來指向元素或子元件的引用資訊。如果沒有提供這個參數,會使用裝飾器後面的屬性名充當參數
import { Vue, Component, Ref } from 'vue-property-decorator'
import { Form } from 'element-ui'
@Componentexport default class MyComponent extends Vue {
@Ref() readonly loginForm!: Form
@Ref('changePasswordForm') readonly passwordForm!: Form
public handleLogin() {
this.loginForm.validate(valide => {
if (valide) {
// login...
} else {
// error tips
}
})
}
}
等同于
export default {
computed: {
loginForm: {
cache: false,
get() {
return this.$refs.loginForm
}
},
passwordForm: {
cache: false,
get() {
return this.$refs.changePasswordForm
}
}
}
}
使用時切記要引入修飾器
import {
Vue,
Component,
Prop,
Component,
Emit,
Provice,
Inject,
Watch,
Model,
Minxins,
} from 'vue-property-decorator'
9、鈎子函數
以下的public、private在引入tslint後是必寫的,否則會有警告,如果沒有引的話是可以不寫的
Ts | Js | 說明 |
---|---|---|
public created() {} | created() {} | 初始化 |
public mounted() {} | mounted() {} | 挂載完畢 |
private _getInitData() {} | methods: { _getInitData() {} } | 方法 |
private get _userName() | {} computed: { _userName() {} } | 計算屬性 |
public destroyed() {} | destroyed() {} | 銷毀生命周期 |
二、狀态管理Vuex (vuex-module-decorators)
傳統的vuex在vue+ts的項目裡面是行不通的,vue 2.0版本對ts的相容性本身并不是特别友好,是以要達到狀态管理的效果,這裡要額外引用一個類庫vuex-module-decorators,它是基于vue-class-component 所做的拓展,它提供了一系列的裝飾器,讓vue+ts結合的項目達到狀态管理的作用。
1、vue-class-component 主要提供了以下的裝飾器,接下來讓我們一一的了解一遍吧
2、先來看看要完成的子產品化管理的目錄結構
.
├─ src/
│ ├─ store/
│ ├─── modules/
│ │ ├─ app.ts
│ │ ├─ user.ts
│ ├─── index.ts
3、動手改造index.ts
import Vue from 'vue'
import Vuex from 'vuex'
import { IAppState } from './modules/app'
import { IUserState } from './modules/user'
Vue.use(Vuex)
export interface IRootState {
app: IAppState
user: IUserState
}
// Declare empty store first, dynamically register all modules later.
export default new Vuex.Store<IRootState>({})
等同于
import Vue from 'vue'
import Vuex from 'vuex'
import app from './modules/app'
import user from './modules/user'
Vue.use(Vuex)
const store = new Vuex.Store({
modules: {
app,
user
}
})
export default store
這樣,子產品化狀态管理的雛形就完成了。對比來看,隻是文法風格的變化,其它的變化不大。ts版的狀态管理最大的改變展現在各個功能功能函數上
4、先看一看原始的vuex配置
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
},
modules: {
}
});
為了顯得不那麼啰嗦,直接上版ts版的狀态管理吧,可以有個直覺的對比
// user.ts
import { VuexModule, Module, Action, Mutation, getModule } from 'vuex-module-decorators'
import store from '@/store'
export interface IUserState {
id_token: string
}
@Module({ dynamic: true, store, name: 'user' })
class User extends VuexModule implements IUserState {
public id_token = ''
@Mutation
private SET_TOKEN(token: string) {
this.id_token = token
}
@Action
public async Login(params: any) {
this.SET_TOKEN(`token!!!`)
}
}
export const UserModule = getModule(User)
解析:
我們看到了一堆的@開頭的裝飾器函數@Mutation @Mutation @Module…
先來一張表格對比一下差異化吧
Ts | Js |
---|---|
public State | state |
@Mutations | mutations |
@Action | action |
get | getters |
(1)定義module
定義一個modules,直接使用裝飾器@Module
注意:原始的vuex同樣有一個名為Module的類,但它不是一個裝飾器,是以别用混淆了
從上面可以看到,我們定義modules不單單用了裝飾器,還帶了參數值,這個是表明是通過命名空間的形式來使用module,如上,這裡的namespaced值即為user
詳細vuex命名空間的說明,可以參考vuex命名空間
除了namespaced,我們看到還有另外一個參數值store,它即為主入口頁對應的整個vuex子產品的store
如果去掉它的話,浏覽器會報錯誤
(2)state
這裡所有的state屬性因為加了tslint都會添加上public修飾,其它的用法都是相似的
(3)Getters
原始的getters計算函數,在這裡對應的即使get方法,即
@Module
export default class UserModule extends VuexModule {
countsNum = 2020
get calculatCount() {
return countsNum / 2
}
}
等同于
export default {
state: {
countsNum: 2
},
getters: {
calculatCount: (state) => state.countsNum / 2
}
}
(4)Mutations
@Mutation
private SET_TOKEN(token: string) {
this.token = token
}
@Mutation
...
等同于
mutations: {
SET_TOKEN: (state, token) => {
state.token = token
},
...
}
說明:
- 兩者的差別其實就是文法糖,原始的Mutation同步方法都是定義在mutations内,而ts版的每一個Mutation都要加上裝飾器@Mutation修飾
- 注意: 一旦使用@Mutation裝飾某一函數後, 函數内的this上下文即指向目前的state,是以想引用state的值,可以直接this.token通路即可。
- Muation函數不可為async函數, 也不能使用箭頭函數來定義, 因為在代碼需要在運作重新綁定執行的上下文
(5)Action
@Action
public async Login(userInfo: { username: string, password: string}) {
...
this.SET_TOKEN(data.accessToken)
}
等同于
actions: {
async Login({ commit }, data) {
...
commit('SET_TOKEN', data.accessToken)
}
}
說明:
異步函數Action和同步函數Mutation使用方法大同小異,差別就是一個是同步,一個是異步,隻要做好區分即可
注意:
- 如果需要在action函數中運作耗時很長的任務/函數, 建議将該任務定義為異步函數*(async methods)*
- 千萬不要使用箭頭函數=>來定義action函數, 因為在運作時需要動态綁定this上下文
5、vuex+ts版的配置搭建成功,接下來我們把它運用到項目中來吧,這裡抽一個登陸頁面的子產品做介紹
import {
VuexModule,
Module,
Action,
Mutation,
getModule
} from 'vuex-module-decorators'
import { login } from '@/api/users' //調用api方法
import store from '@/store'
//聲明user子產品的state變量類型
//export interface 隻是對一個東西的聲明(不能具體的操作)
//export class 導出一個類 類裡面可有參數 ,函數,方法(幹一些具體的事情)
export interface IUserState {
id_token: string
}
@Module({ dynamic: true, store, name: 'user' })
class User extends VuexModule implements IUserState {
public id_token = ''
@Mutation
private SET_TOKEN(token: string) {
//同步存儲id_token變量
this.id_token = token
}
@Action
public async Login(params: any) {
let { mobilePhone, password } = params
const { data } = await login({ mobilePhone, password })
this.SET_TOKEN(`Bearer ${data.id_token}`)
}
}
export const UserModule = getModule(User)
在login頁面中調用
import { UserModule } from '@/store/modules/user'
await UserModule.Login({
...this.loginForm,
router: this.$router
})
把路由對象作為參數傳過去是為了根據不同的響應狀态做判斷,當請求成功後,可以直接應用傳過來的路由對象參數跳轉頁面。
注意:
這一步操作其實是調用了vuex的Action操作,即原始的this. s t o r e . c o m m i t ( ′ a c t i o n ′ ) , 但 是 在 v u e x + t s 項 目 中 , 調 用 異 步 函 數 A c t i o n , 不 需 要 再 用 t h i s . store.commit('action'),但是在vuex+ts項目中,調用異步函數Action,不需要再用this. store.commit(′action′),但是在vuex+ts項目中,調用異步函數Action,不需要再用this.store.commit(‘action’)這種方法,引用子產品後,直接調用裡面的Action方法就好了,同樣的,同步的Mutation也是這樣調用。這些都要歸功于vuex-module-decorators類庫的封裝
好了,調用Action後粗發Mutation同步操作,儲存好token令牌,因為登入之後所有的請求都要把token值放在header頭中發起請求
除了vuex狀态管理,在項目中可能我們還會結合工具類js-cookie一起使用,管理各種變量的值,具體用法跟原始版沒有什麼差別,最主要的是安裝類庫的過程中,還得安裝一個開發ts編譯版