天天看點

TypeScript基礎學習 Vue中引用typescript(3)

參考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編譯版