08Vue+TS實(shí)戰(zhàn)

08Vue+TS實(shí)戰(zhàn)

準(zhǔn)備工作

新建一個(gè)基于 TS 的 Vue 項(xiàng)目:

image.png

在已存在項(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>

  • 泛型

  • 接口

  • 裝飾器

  • 類型聲明

image.png

類型注解和編譯時(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;
}

Interface vs type aliases

泛型

泛型(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 定義中找到歧胁。

參考資料

  1. TypeScript:https://www.typescriptlang.org/

  2. TypeScript 支持 Vue:https://cn.vuejs.org/v2/guide/typescript.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末滋饲,一起剝皮案震驚了整個(gè)濱河市厉碟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌屠缭,老刑警劉巖箍鼓,帶你破解...
    沈念sama閱讀 217,277評(píng)論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異呵曹,居然都是意外死亡款咖,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,689評(píng)論 3 393
  • 文/潘曉璐 我一進(jìn)店門奄喂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)铐殃,“玉大人,你說(shuō)我怎么就攤上這事跨新「焕埃” “怎么了?”我有些...
    開封第一講書人閱讀 163,624評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵域帐,是天一觀的道長(zhǎng)赘被。 經(jīng)常有香客問(wèn)我,道長(zhǎng)肖揣,這世上最難降的妖魔是什么民假? 我笑而不...
    開封第一講書人閱讀 58,356評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮龙优,結(jié)果婚禮上羊异,老公的妹妹穿的比我還像新娘。我一直安慰自己彤断,他們只是感情好野舶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,402評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著瓦糟,像睡著了一般筒愚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上菩浙,一...
    開封第一講書人閱讀 51,292評(píng)論 1 301
  • 那天巢掺,我揣著相機(jī)與錄音,去河邊找鬼劲蜻。 笑死陆淀,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的先嬉。 我是一名探鬼主播轧苫,決...
    沈念sama閱讀 40,135評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了含懊?” 一聲冷哼從身側(cè)響起身冬,我...
    開封第一講書人閱讀 38,992評(píng)論 0 275
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎岔乔,沒想到半個(gè)月后酥筝,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體雏门,經(jīng)...
    沈念sama閱讀 45,429評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡嘿歌,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,636評(píng)論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了茁影。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片宙帝。...
    茶點(diǎn)故事閱讀 39,785評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖募闲,靈堂內(nèi)的尸體忽然破棺而出步脓,到底是詐尸還是另有隱情,我是刑警寧澤蝇更,帶...
    沈念sama閱讀 35,492評(píng)論 5 345
  • 正文 年R本政府宣布沪编,位于F島的核電站,受9級(jí)特大地震影響年扩,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜访圃,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,092評(píng)論 3 328
  • 文/蒙蒙 一厨幻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧腿时,春花似錦况脆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,723評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至徽鼎,卻和暖如春盛末,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背否淤。 一陣腳步聲響...
    開封第一講書人閱讀 32,858評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工悄但, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人石抡。 一個(gè)月前我還...
    沈念sama閱讀 47,891評(píng)論 2 370
  • 正文 我出身青樓檐嚣,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親啰扛。 傳聞我的和親對(duì)象是個(gè)殘疾皇子嚎京,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,713評(píng)論 2 354