TypeScript基礎以及在Vue中的應用

TypeScript推出已經很長時間了,在Angular項目中開發比較廣泛,隨着Vue 3.0的即將推出,TypeScriptVue項目中使用也即將成爲很大的趨勢,筆者也是最近纔開始研究如何在Vue項目中使用TypeScript進行項目的開發。javascript

準備

閱讀博客前但願讀者可以掌握以下技能,文章中也會相對應的講解一些對於TypeScript基礎進行講解。本篇博客基於Vue cli 3.0實踐,若讀者使用的是Vue cli 2.0的話,須要對其webpack配置進行更改,本文不進行講解,請讀者自行百度,並進行更改。css

  1. Vue cli 3.0環境
  2. Vue基礎應用

TypeScript基礎

建立完項目以後,接下來就能夠對項目進行開發,在使用TypeScript開發與使用JavaScript仍是有很大的區別,打開HelloWorld.vue文件內容以下:html

<template>
  <div></div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator';

@Component
export default class HelloWorld extends Vue {

}
</script>

<style lang="scss">

</style>

讀者若常常開發Vue項目會發現與之間的.vue有了一些變化,主要是在<script>標籤部分。使用ES6開發項目時樣文件格式以下:前端

<template>
  <div></div>
</template>

<script>
export default {

}
</script>

<style lang="scss" scoped></style>

對比一下二者之間仍是有很大的區別的。原有寫法是直接使用export default導出一個對象,然而TypeScript而是使用class若是對React的同窗應該很熟悉,有點相似於React建立的組件的寫法了。對於template的使用其實與以前的寫法是同樣的。惟一改變得就是對於<script>部分,若是想在項目中使用TypeScript須要在<script>添加標識:<script lang="ts">告知解析器須要使用TypeScript進行解析。vue

Vue中使用TypeScript編寫項目,須要依賴於Vue提供的包vue-property-decorator以達到對TypeScript的支持。須要使用@修飾器其中Vue功能才能正常使用。不管在頁面仍是組件時,必定要使用@Component對導出類進行修飾,不然沒法正常使用某些功能(如:雙向綁定...)。接下來就來實現如何在模板中渲染數據,這裏將再也不使用data而是直接在class中寫入變量。java

關於vue-property-decorator的用法會在下面詳細介紹。webpack

<template>
  <div>
    <h1>{{message}}</h1>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

@Component
export default class HelloWorld extends Vue {
    private message:string = "Hello,TypeScript";
}
</script>

仔細觀察一下message的聲明,與其以前使用data時已經徹底不同了有沒有,除了沒有使用data之外,還有很大的區別:ios

修飾符  變量名:數據類型 = 數據
private message:string = "Hello,TypeScript";
修飾符

若是沒有接觸過強類型語言的話對於修飾符可能會感受到一絲絲的陌生,在es6class中已經有了修飾符的概念,在es6class中只有一個修飾符static修飾符,表示該類的靜態方法,可是在TypeScript中添加了不少修飾符:程序員

  • public:全部定義成public的屬性和方法均可以在任何地方進行訪問(默認值)
  • private:全部定義成private的屬性和方法都只能在類定義內部進行訪問
  • protected:多有定義成protected的屬性和方法能夠從類定義內部訪問,也能夠從子類中訪問。
  • readonly:readonly關鍵字將屬性設置爲只讀的。只讀屬性必須在聲明時或構造函數裏被初始化。

修飾符不但能夠修飾屬性,還能夠用來修飾方法,若在寫入屬性和方法時沒有使用修飾符對其進行修飾的話默認則會是publices6

class A {
    public message1:string = "Aaron";
    private message2:string = "Angie";
    protected message3:string = "Tom";
    readonly message4:string = "Jerry";
    message5:string = "Sony";
}
class B extends A {
    constructor(){
        //  這裏由於受修飾器影響沒法讀取到`message2`
        //  若寫入 message2 在編碼工具中則會顯示紅色波浪線提示錯誤
        //  Property 'message2' is private and only accessible within class 'A'.
        let {message1,message3,message4,message5} = this;
        console.log(message1,message3,message4,message5);
        //  若去更改 message4 的數據則會拋出下面的錯誤
        //  Cannot assign to 'message4' because it is a read-only property.
        //  this.message4 = "Sun";
    }
}
數據類型

在聲明變量或屬性時須要規定其對應的數據類型是什麼,這樣一來就能夠對其變量以及屬性進行更加嚴格的管理了。

TypeScript中提供了一下基本類型:

  • string: 字符串
  • number:數字
  • boolean:布爾值
  • array:數組
  • enum:枚舉,我的理解枚舉類型並不陌生,它可以給一系列數值集合提供友好的名稱,也就是說枚舉表示的是一個命名元素的集合
  • any:任意類型
  • void:沒有任何類型,一般用於函數沒有返回值時使用
//  定義number型變量
let test:number = 1;
//  定義number型數組
let arr:[]number = [];
let arr1:Array<number> = [];
//  定義對象數據,且對象只能有name字段
let a:{name:string}[] = [];

在進行變量或屬性聲明的時候,一旦使用了數據類型限定的話,若是試圖想要對其數據類型進行更改的話,就會提示一個錯誤,若是類型是多種狀況,可使用|進行分割。若不肯定使用哪一種類型則可使用any

let a:string = "";
// Type '1' is not assignable to type 'string'.
a = 1;

let b:(string|number) = "";
b = 1;

注意:在聲明變量時不必定要使用數據限定,若是沒有使用數據限定,TypeScript則會根據默認值進行數據類型推論做爲其變量的數據類型。

let a = 1;
//  Type '"Aaron"' is not assignable to type 'number'.
a = "Aaron";
函數

對數據已經有了必定了解,以後就是對於函數的說明,在項目開發過程當中惟一不可缺乏的就是函數,一樣的是在class中寫入的方法,一樣也須要使用修飾符,其用法與屬性一致。

在函數後面添加了void標識符,標識當前函數沒有函數返回值,能夠根據當前函數的返回值,更改其返回類型。一旦規定了函數的返回值類型,就沒法再返回其餘的數據的類型,若是強行返回其餘類型的話,則會拋出錯誤。

<template>
  <div>
    <h1>{{message}}</h1>
    <el-button @click="clickMe"></el-button>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator';

@Component
export default class HelloWorld extends Vue {
    private message:string = "Hello,TypeScript";
    
    private clickMe():void{
        alert("Aaron!")
        //  Type '"Angie"' is not assignable to type 'void'.
        //  return "";
    }
}
</script>

有的時候函數會帶有一些參數,該如何去處理這些參數呢?一樣當在接收參數(形參)處一樣也要規定其數據類型,一旦寫入參數在調用該方法時就必須傳入該參數,某些參數若不是必填的,則要在定義其類型前使用?,這樣該參數就不是必填項,能夠有可無,?與默認值不能共存只能使用一個,一旦使用默認值的話,改參數就是一定存在的了,不會出現不傳入的狀況了。

const fn = (name:string,age?:number) => {
    console.log(`my name is ${name}, age ${age || 18}`)
}
fn("Aaron",3);  //  my name is Aaron, age 3
fn("Angie");    //  my name is Angie, age 18
fn();           //  Expected 1-2 arguments, but got 0.

開發過程當中不免會遇到一些特殊的函數,函數內部沒法肯定其參數個數,可是傳入的類型都是統一類型的,在JavaScript中提供了arguments屬性,對於TypeScript有其餘處理方式:

const fn = (...foo:number):number => {
    let res:number = 0;
    //  若是不使用foo,能夠替換成arguments也是同樣的
    for(let i = 0;i<foo.length;i++){
        res += foo[0];
    }
    return res;
}
fn(1,2,3,4,5,6,8,7,9)   //  45

筆者也嘗試使用過arguments,可是若是使用arguments時會拋出一個錯誤Cannot find name 'arguments'.

函數的重載,在沒有接觸過強語言的話多是很陌生的,什麼是重載?重載就是函數或者方法有相同的名稱,可是參數列表不相同的情形,這樣的同名不一樣參數的函數或者方法之間,互相稱之爲重載函數或者方法。(節選自百度百科)

function abs(name:string):string;
function abs(name:{name:string}):string;
function abs(name:string|{name:string}):string{
  if(typeof name === "object"){
    return name.name;
  }
  return name;
}
abs("Aaron");
abs({name:"Angie"});

上述中前兩個都屬於抽象函數,最後一個函數爲對於抽象函數的實現,實現簽名必須兼容全部的重載簽名,老是在參數列表的最後,接受一個any類型或聯合類型的參數做爲他的參數。若是沒有按照實現所傳入參數則會拋出錯誤。

泛型

泛型是程序設計語言的一種特性。容許程序員在強類型程序設計語言中編寫代碼時定義一些可變部分,那些部分在使用前必須做出指明。各類程序設計語言和其編譯器、運行環境對泛型的支持均不同。將類型參數化以達到代碼複用提升軟件開發工做效率的一種數據類型。泛型類是引用類型,是堆對象,主要是引入了類型參數這個概念。

筆者在最開始接觸泛型的時候也是一臉懵逼,由於一直都在聽後端的同窗說,泛型什麼什麼的...一直都只是知道有泛型這個東西,可是泛型應該怎麼用,想問卻又不敢...由於是不知道從何開始問起。

我對於泛型的理解就是去規定一種數據類型,不管是函數接收參數,仍是返回結果,或者一種類型的數組,對象,等等等均可以使用泛型進行約束,泛型就是在定義方法不肯定須要返回什麼樣類型的數值,可是當調用函數值時須要去限定返回該類型。

使用泛型時分爲兩種狀況(只會說起TypeScript的泛型),一種是接口泛型,一種是類泛型其實兩種方法是相似的。

//  類泛型
class User {
  name:string  = "";
  age:number = 0;
}
//  接口泛型
interface User {
  name:string,
  age:number
}

//  獲取數據
function getData(){
  // ... axios操做等
  return [{}];
};
//  T 泛型的形參
//  T 能夠自定義
function getUsers<T> ():T[]{
  return getData();
}
let users:User[] = getUsers<User>();

上面代碼中使用class和接口實現了兩種泛型,兩種都是可用的,通常不推薦使用去使用泛型,由於在使用類去作泛型的時候須要對其中的屬性進行初始化,不然會拋出錯誤。

接口

上面提到了接口,對於接口也是前端沒有涉及的一部分,接口泛指實體把本身提供給外界的一種抽象化物(能夠爲另外一實體),用以由內部操做分離出外部溝通方法,使其能被內部修改而不影響外界其餘實體與其交互的方式。

對於接口來講,去定義一些抽象的方法或屬性,在使用時對其進行實現,使用implements關鍵字對其接口進行實現,能夠同時實現多個接口以,分割。

接口能夠繼承類,可是類不能夠繼承接口,只能實現接口,若是接口繼承類的話,不會繼承類的實現只會繼承類的簽名規範。

class Friend {
  public friends:object[] = [];
  getFriends(){
    return this.friends;
  }
}
interface U extends Friend {
  name:string,
  age:number,
  seyHi(name:string):string
}
interface S {
  job:string
}
class User implements U,S {
  public friends:object[] = [];
  constructor(
    public name:string,
    public age:number,
    public job:string){}
  seyHi(name:string):string{
    return `Hi ${name} ~,my name is ${this.name}`;
  }
  getFriends(){
    return this.friends;
  }
}
new User("Aaron",3,"Code");

有關於TypeScript的類,與es6中的類是同樣的,這裏着重說一下抽象類。抽象類是對其屬性方法進行規定,在繼承時對其進行實現。

abstract class Animal{
  public name:string;
  constructor(name:string){
      this.name=name;
  }
  //  抽象方法 ,不包含具體實現,要求子類中必須實現此方法
  abstract eat():any;
  //  非抽象方法,無須要求子類實現、重寫
  run(){
    console.log('非抽象方法,不要子類實現、重寫');
  }
}
class  Dog extends Animal{
  //  子類中必須實現父類抽象方法,不然ts編譯報錯
  eat(){
    return this.name+"吃肉";
  }
}

在類中須要對其內部的屬性進行初始化賦值,能夠寫入默認值,一樣也能夠根據傳入的值進行賦值,也就是在進行實例化的時候傳入參數對其屬性進行初始化。如下三種方法均可覺得類中的屬性進行初始化。

//  第一種方法
class A {
  public name:string = "Aaron";
}
//  第二種方法
class A {
  public name:string;
  constructor(name:string){
    this.name = name;
  }
}
//  第三種方法
class A {
  constructor(public name:string){}
}

Vue中使用TypeScript

上面已經對TypeScript的基礎作了一些簡單的講解,在開發中是沒有問題的了,接下來就開始在Vue項目中開始實戰。

對於建立項目就不作過多贅述了,只須要在建立項目時選中TypeScript便可,其餘配置項讀者能夠根據項目需求自行選擇。

Vue中使用TypeScript編寫項目之後,不少東西發生了變化,上面已經提到了事件與數據,對一些經常使用的方法進行簡單的說明。

組件傳值

父組件傳入子組件的值經過vue-property-decoratorProp進行接收,傳入的方式與Vue中的使用是相同的。

import { Component, Prop, Vue } from 'vue-property-decorator';
@Component
export default class HelloWorld extends Vue {
  @Prop() private msg!: string;
}
組件掛載

組件掛載則是在Component中進行掛載,除了位置發生了變化,其餘並無任何不一樣。

import {Component, Vue } from 'vue-property-decorator';
import Search from '@/components/business/Search.vue';
@Component({
  components: {
    Search
  }
})
export default class CreateItem extends Vue {}

經過上述代碼已經能夠正常使用組件了<Search/>

事件消息

事件消息使用Emit進行返回事件

注意:

  1. 當使用Emit時執行事件函數所返回的值,則做爲在函數中傳給父組件的值。
  2. Emit中能夠接收參數,第一個參數能夠自定義事件名稱,參數什麼樣,在父組件綁定的事件名稱必須一致,不然事件不會執行,若是不傳入事件名稱,則會默認使用子組件中觸發事件的函數名稱,在父組件調用時則使用烤串形式進行拼接。
import {Component, Vue, Ref, Emit} from 'vue-property-decorator';
@Component
export default class CreateItem extends Vue {
  @Emit("clickMe")  //  執行事件 @clickMe
  private async onClickMe():string{
    return "onClick";
  }
  @Emit             //  執行事件 @on-click-me
  private async onClickMe():string{
    return "onClick";
  }
}
Ref
<template>
  <div>
    <button ref="btn"><button/>
  </div>
</template>

<script lang="ts">
import {Component, Vue, Ref} from 'vue-property-decorator';
@Component
export default class CreateItem extends Vue {
  //  定義類型能夠根據當前爲何元素寫入對應的名稱的element
  //  若是不肯定元素能夠直接寫 HTMLElement
  @Ref("btn") readonly formEle!:HTMLButtonElement;
};
</script>
mixins

對於混入的話有兩種混入方法,能夠依賴於vue-class-component模塊中的混入,也能夠在Component中進行混入。

第一種方法:

//  混入文件
import { Vue, Component} from 'vue-property-decorator';

@Component
export default class myMixins extends Vue {
  values: string = 'Hello'

  created() {
    console.log("混入第二種方法!!!")
  }

}

//  混入
import HomeMixins1 from "@/mixins/Home1";
@Component({
  mixins:[HomeMixins1]
})
export default class Home extends Vue {
    
}

第二種方法:

//  混入文件
import Vue from 'vue';
import Component from 'vue-class-component';

@Component
export default class HomeMixin extends Vue {
  valueq: string = "Hello"

  created() {
    console.log("混入第一種方法!!")
  }
}
//  混入
import  Component,{mixins}  from 'vue-class-component';
import HomeMixins from "@/mixins/Home";
export default class Home extends mixins(HomeMixins) {}

兩種混入方法都是可行的,一樣均可以混入多種方法,官網推薦使用第二種方法進行混入。

過濾器

過濾器分文兩種,一種全局過濾器,一種局部過濾器,對於全局過濾器幾乎沒有發生變化。

全局過濾器

全局過濾器寫在main.ts

Vue.filter('capitalize', function (value:string) {
  if (!value) return ''
  return value.charAt(0).toUpperCase() + value.slice(1)
})

局部過濾器

局部過濾器寫在各個組件@Component中。

import {Component, Vue} from 'vue-property-decorator';

@Component({
  filters:{
    thumb(url:string){
      return url += "/abcd";
    }
  }
})
export default class Home extends Vue {}

項目結構

簡單說一下筆者在使用Vue開發項目式使用的項目結構,可能不是最好的,可是對於項目的可維護性確實有了很大的提升。在項目開始時須要建立項目結構,Vue項目中爲了更好的結構化,須要將項目進行分層處理,以達到高度維護的目的。

目錄結構:

├─api           //  數據請求
├─assets        //  資源
├─components    //  組件
│  ├─basis          //  基礎組件
│  └─business       //  業務組件 
├─domain        //  業務
├─interface     //  接口
├─middleware    //  中間件
├─mixins        //  混入
├─style         //  樣式
├─store         //  狀態管理
├─router        //  路由
└─views         //  視圖

上面對其項目進行了項目結構進行了劃分,若對工程化不太瞭解的同窗可能不太能理解每一層的目的究竟是爲了什麼?他們相互之間又應該如何搭配使用?簡單對每一層進行簡單的描述。

  1. api:其中封裝的是請求後端接口數據的請求函數
  2. assets:靜態資源
  3. components:組件,在文件中分爲了兩個文件夾,業務組件和基礎組件
  4. domain:項目中的業務邏輯
  5. interface:用戶TypeScript限制接口數據格式
  6. middleware:Vue項目中使用的中間件
  7. mixins:須要混入的內容
  8. style:樣式
  9. store:全局狀態管理
  10. router:路由

這裏須要說明一點,在建立項目時若是讀者選擇了vue-routervuex,建立項目後會自動生成router.tsstore.ts筆者這裏並無使用原有的文件,而是單獨對其進行處理。考慮到若項目業務較多可使用使用文件或文件夾再對其路由和狀態管理根據其業務對其進行文件劃分,單獨維護,這樣更加的有利於項目的可維護性。

總結

文章篇幅過長,用了過長的篇幅講解了TypeScript簡單應用,對於Vue中的使用也只是簡單的講解了一下。與其以前是很相似的。

文章些許潦草,可是很感謝你們可以堅持讀完本篇文章。若文章中出現錯誤請你們在評論區提出,我會盡快作出改正。

相關文章
相關標籤/搜索