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中的使用也只是简单的讲解了一下。与其以前是很相似的。

文章些许潦草,可是很感谢你们可以坚持读完本篇文章。若文章中出现错误请你们在评论区提出,我会尽快作出改正。

相关文章
相关标签/搜索