TypeScript 在 Vue 的实践

前言

在 vue-cli 3.0 的脚手架出来以后,官方我们提供了一套 Vue 的 TypeScript 模板,解决了许多模块以及类型问题,官方的东西真香,因此可以使用 TypeScript 搞一波事情。

基础配置

code-7.png

配置默认是全家桶,其中预处理器建议使用 less,如果使用 sass 可能会因为各种莫名其妙的原因安装不上 node-sassbabel 也是必选的,目的是将 TypeScript 编译后的代码转变成 ES5 的代码,提供低版本浏览器支持。

code-8.png

VScode 的插件配置,基本上安装 TypeScript Extension Pack 这个插件以后附带的几个插件够用了(我是一个强迫症,能少安装插件就尽量少安装插件)。然后需要额外安装一个 TSlint Vue 插件,因为 VScode 对 .vue 单文件的支持并不是很好,TSlint 不能有效纠错,需要这个插件配合。

code-10.png

这是生成的默认配置。其中 tsconfig.json 里会设置 src/xxx 的别名为 @/xxx,但是 VScode 是不能识别的,所以需要自行新建一个 jsconfig.json 文件。

// jsconfig.json
{
  "compilerOptions": {
    "baseUrl": ".",
    "module": "commonjs",
    "paths": {
      "@/*":["./src/*"]
    }
  }
}

开始开发

基础使用

import { Vue, Component, Prop, Watch } from 'vue-property-decorator'
import { State, Getter, Mutation, Action } from 'vuex-class'
import { Bind, Debounce } from 'lodash-decorators'
import { UBT } from '@/decorator'
import HelloWorld from '@/components/HelloWorld.vue'
import BasicMixin from '@/mixin/PrintMixin'

@Component({
  components: {
    HelloWorld
  },
  mixins: [BasicMixin]
})
export default class EleComponent extends Vue {
  @Prop({ default: 'Hello' })
  private text!: string

  @State(state => state.user)
  private user!: string

  @Getter('email')
  private email!: string

  private msg: string = 'Hello Element'
  private name: string = 'Typescript'

  private get userInfo (): string {
    return this.text + this.name
  }
    private set userInfo (val: string) {
    this.text = val
  }

  @Mutation('setUserEmail')
  private setUserEmail!: (email: string) => void

  @Action('getUserInfo')
  private getUserInfo!: (params: {token: string}) => Promise<any>

  @Watch('name', { deep: true })
  private onNameChange () {
    console.log('name has been changed!')
  }
  
  @UBT('click', Date.now())
  @Bind()
  @Debounce(300)
  private handleClick () {
    console.log('click', this.text)
  }
}

templatestyle 部分和普通的 js 差不多,这里只贴出 script 部分的代码。基本上就是把传统的配置对象改为了基于 class 的组件,传递的 props、watch、computed 以及 Vuex 的相关属性都通过装饰器实现。

vue-property-decorator 提供 Vue 基本属性的实现。注意一定要使用 @compoenet 去修饰这个组件,否则其它的装饰器无法正常使用。@component({option}) 中接收的参数 option 就是传统的配置,mixin 和子组件的注册都要在这里声明。
vuex-class 提供的是与 Vuex 相关的装饰器,具体用法参考文档。美中不足的是,Store 的定义还是基于配置的,因此 TypeScript 无法正确推导出其方法的签名,并且通过装饰器在组件中声明的方法也是没有签名,所以在组件中需要自行补上方法的签名。

最后一部分实现了一个方法 handleClick 并且使用了三个装饰器进行修饰。主要的目的是实现点击事件的防抖,lodash-decorators 提供了相关的装饰器。传统的 vue 组件如果需要实现的一个防抖事件需要这样写

{
    methods: {
        debounceClick: _.debounce(this.handleClick, 300)
    }
}

这样做是为了 this 指向正确,Vue 会自动为 methods 中的方法绑定 this,但是这样的实现既不优雅也不通用,基于 class 的组件我们只需要 BindDebounce 两个装饰器就能完成,并且在 React 中也是通用的

使用 Mixin

mixin 在 Vue 中使用到的场景很多,其目的是在组件中复用相同的功能代码,但是这种实现并不优雅,它仅仅是功能上实现复用,结构上并没有拓展功能,并且会破坏组件原有的结构,特别是基于 class 的组件。不过传统的 Vue 组件使用 JavaScript 这种类型推断本来就没有,所以显得不重要。希望 Vue 3.0也能像 React 一样实现通过 HOC 复用代码。在 TypeScript 中,不能再像原来一样写基于配置的 mixin 对象,而应该也写为一个 Vue 的子类:

import { Vue, Component } from 'vue-property-decorator'

export default class BasicMixin extends Vue {
  private msg!: string

  private printWords (): void {
    console.log(this.msg)
  }
}

在需要的注入的组件中通过 @component 注入,需要注意的是,如果注入的 class 需要使用被注入组件的属性,需要通过 priavte msg!: string 强制断言属性存在,才能正常使用;同理,如果组件需要使用注入类的方法,也要强制断言。如果只是 template 中使用方法,那么不需要强制断言

填坑指南

  1. VScode 插件配置 TSLint Vue
  2. mixin 的相关配置
  3. Vuex 方法的接口实现
  4. 复用接口的摆放位置
    使用了 TypeScript 以后必然需要声明许多接口。个人觉得有必要定义的接口有:
    1. 后台返回的数据结构,这样能够避免每次都打开 network 看返回的数据结构格式;
    2. 组件内部复用的数据结构,一些数据结构是前端生成的并且在多个组件复用,这些需要提取出来写成接口;
      在接口文件存储的位置上一般分为两类:
    3. 统一定义在 @/interface 通用的接口提取出来放到这个地方;
    4. API 请求文件中,我按照页面的粒度分离了请求 API 的方法,页面级的接口文件也定义在这里,这样在导入请求方法时也可以同时导入接口声明;
  5. get set 的使用
    TypeScript 中不再使用 computed 定义计算属性,而是通过 class 本身的 get set 定义,使用的方式和原来相同
  6. 路由的组件导航守卫失效
    路由的导航钩子不属于 Vue 本身,这会导致 class 组件转义到配置对象时导航钩子无效,因此如果要使用导航钩子需要在 router 的配置里声明
  7. axios 填坑
    使用 axios 请求数据时,它会将数据再包裹一个 AxiosPromise 的接口,具体实现是
export interface AxiosPromise<T = any> extends Promise<AxiosResponse<T>> {
}

export interface AxiosResponse<T = any>  {
  data: T;
  status: number;
  statusText: string;
  headers: any;
  config: AxiosRequestConfig;
  request?: any;
}

通常我们会在 axios.interceptors.response.use 这个拦截方法中取出 res.data,但是这样会导致 axios 返回数据的类型推断失败(即使取出来了,axios 还是会认为返回的是 AxiosPromise ),因此需要这样定义:

export const GET_CITY = (form: IQuery, { target, page, rows }: IParams): Promise<IRes<IRankList>> => {
  const query = Object.assign({}, form, { target, page, rows })
  return ajax.post(URL.city, query).then(res => res.data)
}

const res: IRes<IRankList> = await GET_CITY()

每个返回的结构都需要手动 then(res => res.data) 这样返回的才是 Promise<T>, await 才能正确的取出结构

总结

目前看来 Vue 对 TyepeScript 的支持并不算完善,因为 2.x 版本的 Vue 即使写了 class-compoent,最终也会被编译成配置式的组件。许多 Vue 中方便的 API 以及 Vuex 的方法也只能通过装饰器实现,这导致了方法签名的丢失;通过 ref 属性获取到的子组件实例的类型也不正确,只是一个普通的 Vue 实例并不是定义的 class 类型(在组件内部通过 private public 定义的方法,父组件调用时是无法使用的,React 则实现了这个功能);子组件需要的参数声明也不具有强制性,参考 React 组件参数传递是具有强约束力并且能静态检测,目前 Vue 仍然是在运行时抛出

不过好消息是,Vue 3.0 将采用 TypeScript 重构,全新的 Vue 不仅带来性能上的提升,还会进一步提升对类型的支持。未来,class-compoent 也将成为主流,现在写 TypeScript 以后进行 3.0 的迁移会更加方便。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,681评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,710评论 2 377
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,623评论 0 334
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,202评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,232评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,368评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,795评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,461评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,647评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,476评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,525评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,226评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,785评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,857评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,090评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,647评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,215评论 2 341

推荐阅读更多精彩内容