简单粗暴 · 手把手教你在Vue项目中使用Typescript

3,022 阅读3分钟

题图:Perfect Office by Yegor Meteor

前言

近几个月来,关于Typescript的讨论越来越多,在阅览相关资料后,笔者也开始了自己的学习之旅,尝试在Vue项目中使用,虽然其他大大也分享了相关的教程,但在实际使用过程中难免有所纰漏,抑或随着时间出现更好的方式,因为就有了这篇文章,分享我的构建过程和一些使用技巧。

下面分享的分享内容将不会涉及过多 Typescript 概念,对 Typescript 尚未入门的朋友可以先把文章点赞收藏一下🐶,回头再来阅读,有过后端开发经验的朋友,上手时间会更快一点,比如我在大学时期就学习过 Java语言。

深入理解 TypeScript

🚀 创建项目

首先让我们创建一个Vue项目,熟练地打开命令行,输入创建命令:

vue create vue-ts-demo

因为是Demo项目,特性依赖配置我仅额外选择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>

页面效果:

🗃 ️拓展:如何在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>

页面效果:

⌚️ 最后

以上就是这篇文章的全部内容了,完整项目已上传至我的Github仓库。感谢各位老哥,各位老妹的阅读,第一次分享文章,可能会有不完善不严谨地地方,或者大家有更好的方法,也欢迎在评论区指出,感谢阅读。🙏

如果这篇文章能给你带来一些积极的影响,希望各位能给我点赞收藏 or Github Star(卑微🐶),再次感谢大家。