浅学Typescript在Vue2中的应用

229 阅读3分钟

Ts简介

TypeScript 是 JavaScript 的一个超集,支持 ECMAScript 6 标准。应用到ts无疑是看重这几个优点:

  • 静态类型检查和类型推断
  • IDE 智能提示
  • 代码重构
  • 可读性 废话少说,上代码
//常见类型有 string,number,boolean,undefined,null,symbol
//举个栗子
let a:string;
a = "测试"; //正确
a = 2; //错误

// 当然编译器类型的推断,可省略冒号后面这个语法
let b = true;
// b = 2; // 错误

//类型数组
let arr: string[];
arr = ["joe"]; 

//其中any类型也可用于数组
let arrAny: any[];
arrAny = [2, false, "hello"];
arrAny[1] = 10;

// any类型
let varAny: any;
varAny = "world"; //正确
varAny = 3; //正确

// 在函数中的类型约束
function person(name: string): string {
  return "hello, " + name;
}

// void类型,常用于没有返回值的函数
function fun(): void {}

//类型别名 和交叉类型
type obj = {foo:string;bar:string};
let objType:obj;
objType = {foo:"foo",bar:"bar"};

type OBJ = obj & {flag:boolen};

// 联合类型
let union: string | number;
union = "2";
union = 2;

// 函数
// 1.形参声明就是必填项
// 2.?表示可选
function person(name: string, age = 20, sex?: string): string {
  return "Hello, " + name;
}
person("joe");

// 接口:只定义结构,不定义实现
interface NANE {
  firstName: string;
  lastName: string;
}

function person(name: NANE) {
  return "Hello, " + NANE.firstName + " " + NANE.lastName;
}
person({ firstName: "Jane", lastName: "User" }); // 正确
// person({firstName: 'Jane'}); // 错误

// 泛型优点:
// 函数和类可以⽀持多种类型,更加通⽤
// 不必编写多条重载,冗⻓联合类型,可读性好
// 灵活控制类型约束
// 不仅通⽤且能灵活控制,泛型被⼴泛⽤于通⽤库的编写

// 泛型
interface msg<T> {
  code: 0 | 1;
  data: T;
}

// 泛型⽅法
function getMsg<T>(data: T): msg<T> {
  return {code:1, data};
 }
 
 getMsg<string>('haha')

大概列举了以上一些常见的用法,想了解更多的语法的,请上ts中文手册查看,此外介绍一款好用的工具:ts演习场

如何应用

1、在已存的vue2项目中,运行 vue add @vue/typescript.这种方式就像运行插件一样,勾选你所选需要的配置就好了。

2、新建项目时,vue create XXX,也会出现ts的勾选和vue2vue3的配置勾选,大家按需所求。

这里我们按照第2种方式来走一遍。运行vue create XXX后,配置大概如下:

fb776bf3454bc3993ff020b6d1a45f2.png

等xxx都建好后,我们打开这个项目,回看到这样的目录结构

4247808e5c29dedc2eb1ac8bca68e08.png

主要涉及 shims-tsx.d.tsshims-vue.d.ts 两个文件

  • shims-tsx.d.ts ,允许你以 .tsx 结尾的文件,在 Vue 项目中编写 jsx 代码
  • shims-vue.d.ts是为了 typescript 做的适配定义文件,因为.vue 文件不是一个常规的文件类型,ts 是不能理解 vue 文件是干嘛的.
/**
 * shims-vue.d.ts的作用
 * 为了 typescript 做的适配定义文件,因为.vue 文件不是一个常规的文件类型,ts 是不能理解 vue 文件是干嘛的,
 * 加这一段是是告诉 ts,vue 文件是这种类型的。
 * 可以把这一段删除,会发现 import 的所有 vue 类型的文件都会报错。
 */

declare module '*.vue' { //declare声明宣告, 声明一个ambient module(即:没有内部实现的 module声明) 
  import Vue from 'vue'
  export default Vue
}

declare module 'vue-echarts'  // 引入vue-echarts

<script lang="ts">
    /* eslint-disable @typescript-eslint/camelcase */
    import { Vue, Component, Watch } from 'vue-property-decorator'
    import ECharts from 'vue-echarts' //报错,按上面的方法在shims-vue.d.ts文件中引入即可
    import 'echarts/lib/chart/line'
    import 'echarts/lib/chart/pie'
    import 'echarts/lib/component/tooltip'
</script>

然后我们点进去Home组件中,可以发现它的写法跟以前不太一样了。 vue-class-component 是官方推出的vue对typescript支持的装饰器(库),可以将Vue中的组件用类的方式编写,vue-property-decoratorvue-class-component 的超集。

49058e7b591b80ac570a3905b40b7a6.png 这个组件完全依赖于vue-class-component.它具备以下几个属性:

  • @Component (完全继承于vue-class-component)
  • @Emit
  • @Inject
  • @Provice
  • @Prop
  • @Watch
  • @Model
  • Mixins (在vue-class-component中定义); 更多装饰器的用法可以参考官网文档和掘金# 曾小曾的文章

用法 & 栗子

1、修改Home父组件的代码

<template>
  <div class="home">
    <img alt="Vue logo" src="../assets/logo.png">
    <HelloWorld msg="Welcome to Your Vue.js + TypeScript App" @add-feature="addFeature"/>
  </div>
</template>
//Vue页面中的script部分要加一个lang=ts,这样安装好typescript正能引用
<script lang="ts">
import { Component, Vue } from 'vue-property-decorator'

//FeatureSelected是从HelloWorld子组件曝光出来的类型结构
import HelloWorld, { FeatureSelected } from '@/components/HelloWorld.vue' 

//@Component 类装饰器 挂载HelloWorld组件
@Component({
  components: {
    HelloWorld
  }
})
export default class Home extends Vue {
  // 监听HelloWorld子组件
  private addFeature (feature: FeatureSelected) {
    // HelloWorld子组件传过来的值
    console.log('add a new feature:', feature.name)
  }
}
</script>

2、魔改Helloworld子组件的代码

<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
    <ul>
      <li v-for="item in features" :key="item.id">{{item.name}}</li>
      <li>特性总数:{{ count }}</li>
    </ul>
    <p><input type="text" @keydown.enter="addFeature"></p>
  </div>
</template>
<script lang="ts">
// 导入所需的装饰器
import { Component, Prop, Vue, Emit, Watch } from 'vue-property-decorator'

// 类型别名
 type Feature = {
    id:number;
    name:string;
  };

// 导出交叉类型
export type FeatureSelected = Feature & {selected:boolean};

// 泛型
interface Result<T> {
  ok: 0 | 1;
  data:T;
};

// 泛型方法采用promise
function getResult<T> (): Promise<Result<T>> {
  const data: any = [
    { id: 1, name: '111', selected: false },
    { id: 2, name: '222', selected: true }
  ]
  return Promise.resolve({
    ok: 1,
    data
  })
}

@Component
export default class HelloWorld extends Vue {
  // 1、从父组件Home.vue传过来的值,其中!代表这里一定有值,让编译器别瞎操心。
  // 2、但如果写?的时候再调用,typescript会提示可能为undefined
  // 3、如果有多个不同的值传过来,可以写多个 @Prop
  @Prop() private msg!: string;

  // 相当于定义data值
  features:FeatureSelected[] = [];

  // 生命钩子
  async created () {
    this.features = (await getResult<FeatureSelected[]>()).data
  }

   // 监听 - 装饰器
   @Watch('features')
  private onFeauturesChange (val, old) {
    // 输出变化后的值
    console.log(val)
    console.log(val)
  }

  // @Emit装饰器 - 向父组件传递信息
  @Emit()
   addFeature (e:KeyboardEvent) {
     // 断言,可以由用户判断其类型
     const inp = e.target as HTMLInputElement
     console.log(inp.value)
     const feature: FeatureSelected = {
       id: this.features.length + 1,
       name: inp.value,
       selected: false
     }
     this.features.push(feature)
     inp.value = ''
     // 返回值作为参数
     return feature
   }

  // 寄存器作为计算属性
  get count () {
    return this.features.length
  }
}
</script>