簡單粗暴 · 手把手教你在Vue專案中使用Typescript

heyheyian發表於2020-04-05

簡單粗暴 · 手把手教你在Vue專案中使用Typescript
題圖:Perfect Office by Yegor Meteor

前言

近幾個月來,關於Typescript的討論越來越多,在閱覽相關資料後,筆者也開始了自己的學習之旅,嘗試在Vue專案中使用,雖然其他大大也分享了相關的教程,但在實際使用過程中難免有所紕漏,抑或隨著時間出現更好的方式,因為就有了這篇文章,分享我的構建過程和一些使用技巧。

下面分享的分享內容將不會涉及過多 Typescript 概念,對 Typescript 尚未入門的朋友可以先把文章點贊收藏一下?,回頭再來閱讀,有過後端開發經驗的朋友,上手時間會更快一點,比如我在大學時期就學習過 Java語言。

深入理解 TypeScript

? 建立專案

首先讓我們建立一個Vue專案,熟練地開啟命令列,輸入建立命令:

vue create vue-ts-demo

簡單粗暴 · 手把手教你在Vue專案中使用Typescript

簡單粗暴 · 手把手教你在Vue專案中使用Typescript

因為是Demo專案,特性依賴配置我僅額外選擇Typescript一項配置,剩下的可以根據自己需要和習慣配置,等待片刻就能完成構建過程了。?

? 目錄分析

簡單粗暴 · 手把手教你在Vue專案中使用Typescript

可以從package.json檔案中看到,與普通專案相比,開發依賴多了typescript一項,執行依賴則是多了vue-class-componentvue-property-decorator,讓我看下他們的作用是什麼:

Vue-class-component:一個允許您以類風格的語法建立Vue元件的庫。更多介紹可檢視vue-class-component 官方文件

Vue-property-decorator:依賴於vue-class-component,它提供@Component @Prop @Watch @Emit等特性的裝飾器,與我們在普通專案中使用的特性相同


此外,src目錄下還多了兩個檔案:shims-vue.d.tsshims-tsx.d.ts

shims-vue.d.ts:讓.ts檔案能夠識別.vue檔案

shims-tsx.d.ts:允許我們在專案是編寫.tsx檔案

然而在實際在開發中,我們需要引入第三方依賴,因此我更傾向於對shims-vue.d.ts檔案進行簡單的改造

// shims-vue.d.ts
// 主要引入vue官方依賴和一些自定義的全域性外掛

import Vue from 'vue';
import VueRouter, { Route } from 'vue-router';
import { Store } from 'vuex';

declare module 'vue/types/vue' {
  interface Vue {
    $router: VueRouter;
    $route: Route;
    $message: any; // 一個全域性的提示外掛
  }
}
複製程式碼
// 新建 vue.d.ts 檔案,與shims-vue.d.ts同一層級
// 這裡主要是宣告一些第三方依賴,避免在使用中出現報錯

declare module '*.vue' {
  import Vue from 'vue';
  export default Vue;
}

declare module 'qrcode'; // 一個第三方的二維碼庫
declare module '*.scss'; // scss檔案
declare module '*.tsx'; // tsx檔案
declare module '*.js'; // js檔案
複製程式碼

最後,專案中一共有shims-vue.d.ts vue.d.ts shims-tsx.d.ts 三個宣告配置檔案

?️ 正式體驗

專案配置好了,下面我們來做個Demo,這裡選用的是Demo屆的傳統題材:TodoList

首先我們定義元件TodoItem.vue用作顯示待辦項,下面是說明和程式碼:

  • script 標籤中需要宣告lang為ts;
  • 通過vue-property-decorator引入Compoent、Prop、Vue
  • Todo的型別定義為ITodo的介面,包含 string 型別的 content 和 Date 型別的 time 兩個屬性;
  • @Prop中比較常用的屬性有requireddefault,分別設定必須項和預設值;
<template>
  <div
    class="todo-item"
    @click="complete">
    {{todo.content}}
  </div>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'
import ITodo from '@/types/ITodo' // { content: string, time: Date }

@Component({
  name: 'TodoItem'
})
export default class extends Vue {
  @Prop({ required: true }) private todo!: ITodo;

  public complete () {
    this.$emit('click')
  }
}
</script>
複製程式碼

然後則是TodoList.vue頁面,它用作新增待辦項和待辦項列表的顯示,說明和程式碼如下:

  • 同上,我們需要引入 Todo 型別介面及vue-property-decorator
  • 引入TodoItem.vue元件,並在 @Component 中 components 屬性中宣告;
  • 定義 Data、Computed、methods 時,常用private/public/protected 宣告作用域;
  • 定義屬性時,可以不宣告屬性的型別,ts將自動根據初始值判斷型別,但宣告陣列時,若不宣告型別將出現報錯資訊,可比較程式碼中的contenttodos
  • 定義computed時,使用get xxx() { return ... } 來定義,如下todoCount
  • 在儲存輸入框內容的save方法中,我們需要執行一個 blur() 的操作,我們通過 $ref 獲取到輸入框,但此時若不顯式宣告型別,編輯器將會報錯,原因是 ts 無法獲悉我們獲取的元件型別,自然無法判斷 blur() 方法是否存在於元件上;
<template>
  <div class="todo-container">
    <div class="todo-header">? Todo List ?</div>
    <div class="todo-content">
      <!-- input field -->
      <div class="title">新增</div>
      <input
        v-model="content"
        ref="input"
        placeholder="✏️ 寫點什麼唄..."
        class="todo-input-field"
        @keydown.enter="save"
      />
      <div class="title">待完成{{todoCount}}</div>
      <!-- todos -->
      <todo-item
        v-for="(todo, i) in todos"
        :key="todo.time.getTime()"
        :todo="todo"
        @click="complete(i)"/>
      <!-- default -->
      <div v-show="todos.length <= 0" class="default-todo-content">
        暫無待完成事項
      </div>
    </div>
  </div>
</template>

<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'
import ITodo from '@/types/ITodo'
import TodoItem from '@/components/TodoItem.vue'

@Component({
  name: 'TodoList',
  components: {
    TodoItem
  }
})
export default class extends Vue {
  // 輸入框內容
  private content = '';

  // 待完成
  private todos: ITodo[] = [
    { content: 'Sleep', time: new Date() }
  ];

  get todoCount () {
    const { length } = this.todos
    return length > 0 ? `(共${length}項)` : ''
  }

  // 將待辦項設為完成
  public complete (index: number) {
    this.todos.splice(index, 1)
  }

  // 儲存輸入框內容
  public save () {
    this.todos.push({
      content: this.content,
      time: new Date()
    })

    this.content = '';
    // 取消聚焦
    (this.$refs.input as HTMLInputElement).blur()
  }
}
</script>
複製程式碼

頁面效果:

簡單粗暴 · 手把手教你在Vue專案中使用Typescript

? ️擴充:如何在Typesctipt中使用Vuex

Vuex 在許多大中型專案中都有用到,下面就介紹一下它在Typescript中的一些使用技巧,我們繼續以上面的 TodoList 作為切入點,我使用的是vuex-module-decoratorsGithub地址,它的使用方法與vue-property-decorator相類似,讓我為大家娓娓道來。

首先通過npm install -D vuex-module-decorators進行安裝;

然後改造store,在src/store中新建modules資料夾,新建檔案todo.ts,相類似地,從vuex-module-decorators引入相關模組,然後以@的形式宣告,程式碼內容如下:

// src/store/modules/todo.ts

import { VuexModule, Module, Mutation, Action, getModule } from 'vuex-module-decorators'
import store from '@/store'
import ITodo from '@/types/ITodo'

interface TodoStore {
  trashes: ITodo[];
}

@Module({ dynamic: true, store, name: 'config' })
class TodoStore extends VuexModule implements TodoStore {
  // 已完成列表
  public trashes: ITodo[] = [];
  
  // 加入已完成列表
  @Mutation
  private ADD_TRASH (todo: ITodo) {
    this.trashes.push(todo)
  }
  
  // 加入已完成列表
  @Action({ rawError: false })
  public addTrash (todo: ITodo) {
    this.ADD_TRASH(todo)
  }
};

export const TodoStoreModule = getModule(TodoStore)
複製程式碼

然後改造src/store/index.ts檔案,內容如下:

import Vue from 'vue'
import Vuex from 'vuex'
import { TodoState } from '@/store/modules/todo'

Vue.use(Vuex)

export interface RootState {
  todoStore: TodoState;
}

export default new Vuex.Store<RootState>({})
複製程式碼

然後在TodoList.vue頁面中引入,並對 TodoList 進行一定的升級,已完成的專案將被放入 store 中:

  • 頁面新增顯示已完成列表
<template>
  <div class="title">已完成{{trashCount}}</div>
    <!-- todos -->
    <todo-item
      v-for="todo in trashes"
      :key="todo.time.getTime()"
      type="trash"
      :todo="todo"/>
      <!-- default -->
      <div v-show="trashes.length <= 0" class="default-todo-content">
        暫無已完成事項
      </div>
  </div>
</template>

<script lang="ts">
import { TodoStoreModule } from '@/store/modules/todo'
@Component({
  name: 'TodoList',
  components: {
    TodoItem
  }
})
export default class extends Vue {
  // 已完成數量
  get trashCount () {
    return TodoStoreModule.trashCount > 0
      ? `(共${TodoStoreModule.trashCount}項)`
      : ''
  }

  // 已完成數量
  get trashes () {
    return TodoStoreModule.trashes
  }

  // 將待辦項設為完成
  public complete (index: number) {
    const targetTodo = this.todos.splice(index, 1)
    TodoStoreModule.addTrash(targetTodo[0])
  }
}
</script>
複製程式碼

頁面效果:

簡單粗暴 · 手把手教你在Vue專案中使用Typescript

⌚️ 最後

以上就是這篇文章的全部內容了,完整專案已上傳至我的Github倉庫。感謝各位老哥,各位老妹的閱讀,第一次分享文章,可能會有不完善不嚴謹地地方,或者大家有更好的方法,也歡迎在評論區指出,感謝閱讀。?

如果這篇文章能給你帶來一些積極的影響,希望各位能給我點贊收藏 or Github Star(卑微?),再次感謝大家。

相關文章