TypeScript

647 阅读4分钟

一.安装

 vue add @vue/typescript

二.vue中三种使用方式

  1. class-style
<script lang="ts">
import { Component, Prop, Vue, Emit } from "vue-property-decorator";

@Component
export default class HelloWorld extends Vue {
  @Prop() private msg!: string; // 这行约束是写给ts编译器的
  
  // 属性将成为data中数据
  features: string[] = ["类型注解", "编译类型语言"];
  
}
</script>
  1. option-stytle
import Vue from 'vue'
export default Vue.extend({
  data(){}
})
  1. 直接创建一个tsx文件(写法类似react)
import { Component, Prop, Vue } from 'vue-property-decorator';
import { CreateElement } from 'vue';

@Component
export default class HelloWorld extends Vue {
  @Prop() private msg!: string;

  onclick(){
    console.log(this.msg);
    
  }
  
  render(h: CreateElement) {
    return <div onClick={this.onclick}>{this.msg}</div>
  }
}

三.TS特点

类型注解、编译时类型检查

备注:使用类型注解约束变量类型,编译器可以做静态类型检查,使程序更加健壮

  1. 类型基础

// 类型注解
let var1: string;

var1 = '曾小白';
// var1=1 // no ok

// 类型推断
let var2 = true;
// var2 = 1 // no ok

// 常见类型: string, boolean, number, undefined, null

// 类型数组
let arr :string[]
arr = ['tom', 'jerry']
// arr = [1,2] // no ok

// 任意类型any
let varAny: any;
varAny = 'tom'
varAny = 1

let arrAny: any[]
arrAny = [1,2,3]

// 函数中的类型约束
function great(person: string): string {
    return 'hello,' + person
}
const res = great('tom')

// void类型,常用于没有任何返回值的函数
function warn(): void {}
  1. 类型别名(使用类型别名自定义类型)
// 可以用下面这样方式定义对象类型
const objType: { foo: string, bar: string }

// 使用type定义类型别名,使用更便捷,还能复用 
type Foobar = { foo: string, bar: string } 
const aliasType: Foobar
 
  1. 接口(接口仅约束结构,不要求实现,使用更简单)
 // Person接口定义了结构 
interface Person {
  firstName: string;
  lastName: string;
}
// greeting函数通过Person接口约束参数解构 function greeting(person: Person) {
  return 'Hello, ' + person.firstName + ' ' + person.lastName;
}
greeting({firstName: 'Jane', lastName: 'User'}); // 正确 
greeting({firstName: 'Jane'}); // 错误
  1. 联合类型(希望某个变量或参数的类型是多种类型其中之一)
let union: string | number;
union = '1'; // ok
union = 1; // ok
  1. 交叉类型(想要定义某种由多种类型合并而成的类型使用交叉类型)
type First = {first: number};
type Second = {second: number};
// FirstAndSecond将同时拥有属性first和second 
type FirstAndSecond = First & Second;
  1. 函数
// 必填参:参数一旦声明,就要求传递,且类型需符合
function greeting(person: string): string {
  return "Hello, " + person;
}
greeting('tom')

//可选参数:参数名后面加上问号,变成可选参数
function greeting(person: string, msg?: string): string {
  return "Hello, " + person;
}

// 默认值
function greeting(person: string, msg = ''): string {
  return "Hello, " + person;
}

//函数重载:以参数数量或类型区分多个同名函数
// 重载1
function watch(cb1: () => void): void;
// 重载2
function watch(cb1: () => void, cb2: (v1: any, v2: any) => void): void; // 实现
function watch(cb1: () => void, cb2?: (v1: any, v2: any) => void) {
  if (cb1 && cb2) { 
    console.log('执行watch重载2');
  } else { 
    console.log('执行watch重载1');
  } 
}
  1. 类class的特性(ts的特性和es6中大体相同,重点关注ts带来的访问控制等特性)
class Parent {
private _foo = "foo"; // 私有属性,不能在类的外部访问 
protected bar = "bar"; // 保护属性,可以在子类中访问
// 参数属性:构造函数参数加修饰符,能够定义为成员属性 
constructor(public tua = "tua") {}
// 方法也有修饰符
private someMethod() {}
// 存取器:属性方式访问,可添加额外逻辑,控制读写性 
// 定义getter作为计算属性
get foo() {
    return this._foo;
  }
set foo(val) {
    this._foo = val;
  } 
}
  1. 泛型(泛型(Generics)是指在定义函数、接口或类的时候,不预先指定具体的类型,而在使用的时候再指定 类型的一种特性。以此增加代码通用性。 )
// 不用泛型
// interface Result {
// ok:0|1;
//   data: Feature[];
// }
// 使用泛型
interface Result<T> {
ok: 0 | 1;
data: T; }
// 泛型方法
function getResult<T>(data: T): Result<T> {
  return {ok:1, data};
}
// 用尖括号方式指定T为string getResult<string>('hello') // 用类型推断指定T为number getResult(1)
// 泛型的优点:
// 1.函数和类可以支持多种类型,更加通用
// 2.不必编写多条重载,冗长联合类型,可读性好
// 3.灵活控制类型约束

四.声明文件

使用ts开发时如果要使用第三方js库的同时还想利用ts诸如类型检查等特性就需要声明文件,类似xx.d.ts

// vue项目中还可以在shims-vue.d.ts中对已存在模块进行补充
npm i @types/xxx
// 利用模块补充$axios属性到Vue实例,从而在组件里面直接用
// main.ts
import axios from 'axios'
Vue.prototype.$axios = axios;

// shims-vue.d.ts
import Vue from "vue";
import { AxiosInstance } from "axios";

declare module '*.vue' {
  export default Vue
}

declare module "vue/types/vue" {
  interface Vue {
    $http: AxiosInstance;
  }
}

五. 装饰器

装饰器用于扩展类或者它的属性和方法。@xxx就是装饰器的写法

  1. 属性声明:@Prop
export default class HelloWorld extends Vue { 
  // Props()参数是为vue提供属性选项
  // !称为明确赋值断言,它是提供给ts的 
  @Prop({type: String, required: true}) 
  private msg!: string;
}
  1. 事件处理:@Emit
// 通知父类新增事件,若未指定事件名则函数名作为事件名(羊肉串形式) 
@Emit()
private addFeature(event: any) {
  // 若没有返回值形参将作为事件参数
  const feature = { name: event.target.value, id: this.features.length + 1 }; this.features.push(feature);
  event.target.value = "";
  return feature;
  // 若有返回值则返回值作为事件参数
}
  1. 变更监测:@Watch
@Watch('msg')
onMsgChange(val:string, oldVal:any){
    console.log(val, oldVal);
}

六. 状态管理推荐使用:vuex-module-decorators

  1. 安装
npm i vuex-module-decorators -D
  1. 根模块清空,修改store/index.ts
export default new Vuex.Store({})
  1. 定义counter模块,创建store/counter
import {Module, VuexModule, Mutation, Action, getModule} from 'vuex-module-

decorators
'
import store from './index'

// 动态注册模块
@Module({dynamic: true, store: store, name: 'counter', namespaced: true})
class CounterModule extends VuexModule {
 count = 1

 @Mutation
 add() {
// 通过this直接访问count
   this.count++
 }

// 定义getters
 get doubleCount() {
   return this.count * 2;
 }

 @Action
 asyncAdd() {
   setTimeout(() => {
// 通过this直接访问add this.add()
   }, 1000);
 }
}

// 导出模块应该是getModule的结果
export default getModule(CounterModule)
  1. 使用,App.vue
<p @click="add">{{$store.state.counter.count}}</p>
<p @click="asyncAdd">{{count}}</p>

import CounterModule from '@/store/counter'
@Component
export default class App extends Vue {
  get count() {
    return CounterModule.count
}
  add() {
    CounterModule.add()
}
  asyncAdd() {
    CounterModule.asyncAdd()
  } 
}

七. 装饰器原理

装饰器是工厂函数,它能访问和修改装饰目标。

  1. 类装饰器
// 类装饰器表达式会在运行时当作函数被调用,类的构造函数作为其唯一的参数。
function log(target: Function) {
// target是构造函数
console.log(target === Foo); // true 
target.prototype.log = function() {
    console.log(this.bar);
  }
}
@log
class Foo {
  bar = 'bar' 
}
const foo = new Foo();
// @ts-ignore
foo.log();
  1. 方法装饰器
// 方法装饰器:区别是参数数量和类型
// target类实例,name:方法名,最后的是描述符
function rec(target: any, name: string, descriptor: any) {
    // 这里通过修改descriptor.value扩展了bar方法
    const baz = descriptor.value;
    descriptor.value = function (val: string) {
        // 扩展功能
        console.log('run method', this.bar);
        // 本来功能
        baz.call(this, val);
        console.log('run method', this.bar);
    }
}
class Foo {
  @rec
  setBar(val: string) {
    this.bar = val
} }
foo.setBar('lalala')