详解Vue2.5+迁移至Typescript指南


Posted in Javascript onAugust 01, 2019

为什么要迁移至Typescript

Javascript本身是动态弱类型的语言,这样的特点导致了Javascript代码中充斥着很多Uncaught TypeError的报错,给开发调试和线上代码稳定都带来了不小的负面影响。

而Typescript提供了静态类型检查,使很多类型错误在编写时就已经发现,不会带到测试阶段。

同时,Javascript不定义model就可以使用一个对象,有人喜欢这样的灵活性,的确这样的语法在model不复杂的时候可以快速的开发出需要的功能,但一旦model庞大,找一个需要的属性值都不知道从何找起。而在Typescript中,我们需要使用TS中的interface type等方式先定义出model,才可以调用其属性值,所以Typescript极大的提高了代码的可读性。

可行性

因为TypeScript是JavaScript的超集,TypeScript 不会阻止 JavaScript 的运行,即使存在类型错误也不例外,这能让你的 JavaScript 逐步迁移至 TypeScript。所以可以慢慢地做迁移,一次迁移一个模块,选择一个模块,重命名.js文件到.ts,在代码中添加类型注释。当你完成这个模块时,选择下一个模块。

如何将已有的Vue项目迁移至Typescript

安装依赖

Vue官方提供了一个库Vue-class-component,用于让我们使用Ts的类声明方式来编写vue组件代码。Vue-property-decorator则是在Vue-class-component的基础上提供了装饰器的方式来编写代码。首先我们需要在package.json中引入这两个依赖。

我的项目是基于vue-cli@3.X创建的,还需要在项目中引入@vue/cli-plugin-typescript typescript两个依赖来完成Typescript的编译。

配置tsconfig.json

在项目根目录新建tsconfig.json,并引入以下代码

{
 "compilerOptions": {
  "target": "esnext",
  "module": "esnext",
  "strict": true,
  "jsx": "preserve",
  "importHelpers": true,
  "moduleResolution": "node",
  "experimentalDecorators": true,
  "esModuleInterop": true,
  "allowSyntheticDefaultImports": true,
  "sourceMap": true,
  "baseUrl": ".",
  "noFallthroughCasesInSwitch":true,
  "noImplicitAny":true,
  "noImplicitReturns":true,
  "noImplicitThis":true,
  "types": [
   "webpack-env"
  ],
  "paths": {
   "@/*": [
    "./app/common/*"
   ],
   "_app/*": [
    "./app/*"
   ],
   "_c/*": [
    "./app/common/components/*"
   ],
   "api/*": [
    "./app/service/*"
   ],
   "assets/*": [
    "./app/assets/*"
   ]
  },
  "lib": [
   "esnext",
   "dom",
   "dom.iterable",
   "scripthost"
  ],
 },
 "include": [   // 在此出填写你的项目中需要按照typescript编译的文件路径
  "app/**/*.ts",
  "app/**/*.tsx",
  "app/**/*.d.ts",
  "app/**/*.vue",
 ],
 "exclude": [
  "node_modules"
 ]
}

特别需要注意的是,现在的vue项目中大多使用了webpack的alias来解析路径,在tsconfig.json中需要配置path属性,让typescript同样认识在webpack中配置的路径别名。

添加全局声明文件

因为在ts文件中是无法识别vue文件的,所以需要在项目根目录新建shims-vue.d.ts文件,添加以下代码,来让ts识别vue文件。

import Vue from 'vue';

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

由下而上的迁移

因为是迁移已经存在的项目,不建议开始就把main.js重命名为main.ts,对于绝大多数Vue项目,main.js引入了太多的依赖,我们应该首先从依赖着手,自下而上的迁移Typescript。对于项目中一些偏底层,甚至是框架维护者所提供的库函数,我们不关心其实现逻辑,所以没有必要将其改写为ts文件,只需要给其加声明文件供我们的业务代码调用即可。

在我的项目中,service层的逻辑非常简单,仅仅是传参数调用接口,没有添加任何其他的逻辑,逻辑如此简单其实没有什么必要改写为ts文件,所以我为service层的文件编写声明文件,来为调用service层的代码提供类型声明。

声明文件编写方法

一个js文件如下

//service.js
import axios from '@/libs/api.request'
export default {
  /**
   * 创建账户
   * @param {Object} data
   * @param {String} data.accountType optinal
   * @param {String} data.username
   * @param {String} data.password
   * @param {String} data.gender X | F | M
   * @param {String} data.email
   * @param {Number} data.level
   */

  createAccount(data) {
    return axios.request({
      url: `/api/account/createUser`,
      method: 'post',
      data: data
    }).then((res) => [res, null]).catch((err) => [null, err]);
  },
}

可以看到,在使用typescript之前,对于一个函数的参数和返回值等信息的提示是通过jsdoc实现的,能够在调用时确定参数类型及名称,但jsdoc毕竟只是注释,并不能提供类型校验,所以在这里我们为其编写声明文件,编写后的声明文件如下

//service.d.ts
interface createAccountParams {
  accountType?: string,
  username: string,
  password: string,
  gender: 'X' | 'F' | 'M',
  email: string,
  level?: number
}
interface createAccountReturn {
  userId: string,
}
export interface Service {
  createAccount(data: createAccountParams): createAccountReturn
}

这样一个service层的接口文件的声明文件就编写完成了,为了获得Typescript和vscode提供的类型提示和校验,在main.js中将service.js导出的实例绑定在了Vue原型上,使得我们可以在vue组件中通过vm.$service方便的访问service实例。但是Typescript并不知道Vue实例上有什么属性,这时需要我们在之前添加的shims-vue.d.ts文件中添加几行代码。

import Vue from 'vue';
import Service from "pathToService/service.d.ts";

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

declare module "vue/types/vue" {
 interface Vue {
  $service: Service
 }
}

得力于typescript中提供的模块补充功能,我们可以在node_modules/vue/types/vue中补充我们需要在Vue上提供的属性。

改写Vue文件

我们需要将原来的vue文件改写成使用vue-property-decorator编写的方式。

<script lang="ts">
import {Component,Vue} from "vue-property-decorator";

@Component
export default class MyComponent extends Vue{
  // 声明data
  form: {
    accountType?: string,
    username: string,
    password: string,
    gender: 'X' | 'F' | 'M',
    email: string,
    level?: number
  } = {
    username:'',
    password:'',
    gender:'X',
    email:''
  };

  // 声明methods
  async submit(): void {
    //调用上面的service接口
    const [res,err] = await this.$service.createAccount(this.form);
  }
}
</script>

至此一个Vue项目迁移至Typescript的过程就已经完成了,剩下的工作就是将代码中其他的文件一步步由js迁移到typescript中。

把方法绑定到Vue实例下

除了我们之前提到过的将自己编写的service挂载到vue实例上,大家一定清楚在vue项目中,我们经常会调用this.$refs this.$router this.$store等等,typescript也会检查这些属性是否被绑定在了vue实例上,那么我们并没有在类型系统中声明这些值,按道理应该报Property '$refs' does not exist on type [your_component_name]
真相是vue vue-router vuex都已经给我们声明了相应的类型,我们可以cd ./node_modules/vue/types/目录中去查看
截取少量代码如下所示:

export interface Vue {
 readonly $el: Element;
 readonly $options: ComponentOptions<Vue>;
 readonly $parent: Vue;
 readonly $root: Vue;
 readonly $children: Vue[];
 readonly $refs: { [key: string]: Vue | Element | Vue[] | Element[] };
 readonly $slots: { [key: string]: VNode[] | undefined };
 readonly $scopedSlots: { [key: string]: NormalizedScopedSlot | undefined };
 readonly $isServer: boolean;
 readonly $data: Record<string, any>;
 readonly $props: Record<string, any>;
 readonly $ssrContext: any;
 readonly $vnode: VNode;
 readonly $attrs: Record<string, string>;
 readonly $listeners: Record<string, Function | Function[]>;
}

只要正常的在依赖中安装了vue-router vuex就已经通过模块补充的方式将类型添加到了vue实例上。

在一些项目中,vue-router vuex这些依赖不是通过安装在依赖中引入的,而是通过index.html引入的cdn资源文件,这样在开发过程中我们就无法获取其类型。

这个时候我们可以通过安装@types依赖的方式将类型系统补充到项目中,如npm install @types/jquery --save-dev。

不幸的是vue-router和vuex的types包已经废弃了,只能通过手动去github上下载对应版本的vue-router vuex将types文件引入到项目中,你可以像我一样在项目中新建一个types目录,引入需要的类型声明文件。

这样就可以直接在vue实例上访问到$store $router等属性了。

同理当你想要引入其他的组件库上的一些类型文件时,也是这样的方式。

一些需要注意的问题

在vue开发过程中我们会使用this.$refs去访问某一个具体实例的方法,但是这在ts中是访问不到的常见的,比如要想要使用form组件中的validate方法,我们需要给其加上类型断言
this.$refs.form.validate()变为(this.$refs.form as Vue & {validate:Function}).validate()
来告诉编译器this.$refs.form上有validate方法。

因为类型断言前提条件是是当 S 类型是 T 类型的子集,或者 T 类型是 S 类型的子集时,S 能被成功断言成 T,所以需要在类型断言时合并Vue类型。

同时也可以通过vue-property-decorator提供给我们的装饰器一劳永逸的将该refs添加到computed属性上

import { Vue, Component, Ref } from 'vue-property-decorator'
import Form from '@/path/to/another-component.vue'
@Component
export default class YourComponent extends Vue {
 @Ref() readonly form!: Form
}

等同于

export default {
 computed() {
  form: {
   cache: false,
   get() {
    return this.$refs.form as Form
   }
  },
 }
}

这样我们就可以通过 this.form.validate()来做表单校验了

新手容易遇到的一些问题

疑问1:interface和type有什么区别?

type 可以声明基本类型别名,联合类型,元组等类型

eg.type a = string;是被允许的,

interface 会自动声明合并

interface person{
  gender:string
  age:number
}
interface person{
  name:string
}

疑问2: 错误 Property 'hideContent' has no initializer and is not definitely assigned in the constructor.

strictPropertyInitialization属性会在strict设置为true时自动被设置为true。

但这个属性并不合理,它要求每个实例的属性都有初始值,我们在tsconfig将其设置为false就好了。

疑问3: 赋值兼容性

interface person {
  name:string
  age:number
}
interface student {
  name:string
  age:number
  stuid:string
}
let person: person= {
  name:'name',
  age:1
}
let student: student = {
  name:'name',
  age:1,
  stuid:'stuid'
};

person = student //这样是可以的
student = person //这样不允许

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
有关DOM元素与事件的3个谜题
Nov 11 Javascript
jQuery EasyUI API 中文文档 - ValidateBox验证框
Oct 06 Javascript
解决3.01版的jquery.form.js中文乱码问题的解决方法
Mar 08 Javascript
jQuery实现百叶窗焦点图动画效果代码分享(附源码下载)
Mar 14 Javascript
详解react-router如何实现按需加载
Jun 15 Javascript
jquery实现限制textarea输入字数的方法
Sep 06 jQuery
浅谈react前后端同构渲染
Sep 20 Javascript
微信小程序下拉刷新界面的实现
Sep 28 Javascript
微信小程序数据存储与取值详解
Jan 30 Javascript
H5实现手机拍照和选择上传功能
Dec 18 Javascript
JavaScript代码模拟鼠标自动点击事件示例
Aug 07 Javascript
Vue Element-ui表单校验规则实现
Jul 09 Vue.js
微信小程序组件传值图示过程详解
Jul 31 #Javascript
vue.js实现回到顶部动画效果
Jul 31 #Javascript
vue实现滑动超出指定距离回顶部功能
Jul 31 #Javascript
Vue实现回到顶部和底部动画效果
Jul 31 #Javascript
详解mpvue实现对苹果X安全区域的适配
Jul 31 #Javascript
vue将后台数据时间戳转换成日期格式
Jul 31 #Javascript
Vue Element UI + OSS实现上传文件功能
Jul 31 #Javascript
You might like
最贵的咖啡是怎么产生的,它的风味怎么样?
2021/03/04 新手入门
ninety plus是什么?ninety plus咖啡好吗?
2021/03/04 新手入门
PHP4(windows版本)中的COM函数
2006/10/09 PHP
用PHP控制用户的浏览器--ob*函数的使用说明
2007/03/16 PHP
php ZipArchive压缩函数详解实例
2013/11/06 PHP
windows服务器中检测PHP SSL是否开启以及开启SSL的方法
2014/04/25 PHP
浅谈Laravel中的一个后期静态绑定
2017/08/11 PHP
PHP多个图片压缩成ZIP的方法
2020/08/18 PHP
Ubuntu 16.04中Laravel5.4升级到5.6的步骤
2018/12/07 PHP
php7 list()、session及其他模块的修改实例分析
2020/05/25 PHP
JS window对象的top、parent、opener含义介绍
2013/12/03 Javascript
JavaScript+CSS控制打印格式示例介绍
2014/01/07 Javascript
jQuery中DOM树操作之使用反向插入方法实例分析
2015/01/23 Javascript
JavaScript中数据结构与算法(二):队列
2015/06/19 Javascript
z-blog SyntaxHighlighter 长代码无法换行解决办法(基于jquery)
2015/11/18 Javascript
浅谈如何实现easyui的datebox格式化
2016/06/12 Javascript
vue制作加载更多功能的正确打开方式
2016/10/12 Javascript
jQuery插件jqGrid动态获取列和列字段的方法
2017/03/03 Javascript
关于在mongoose中填充外键的方法详解
2017/08/14 Javascript
深入理解Node.js中通用基础设计模式
2017/09/19 Javascript
详解vue.js数据传递以及数据分发slot
2018/01/20 Javascript
详细分析python3的reduce函数
2017/12/05 Python
对Python2与Python3中__bool__方法的差异详解
2018/11/01 Python
Python 线性回归分析以及评价指标详解
2020/04/02 Python
解决运行django程序出错问题 'str'object has no attribute'_meta'
2020/07/15 Python
EJB的基本架构
2016/09/22 面试题
自我评价正确写法范文
2013/12/10 职场文书
新学期教师寄语
2014/04/02 职场文书
地球一小时倡议书
2014/04/15 职场文书
我的理想演讲稿
2014/04/30 职场文书
植树节口号
2014/06/21 职场文书
大学生工作求职信
2014/06/23 职场文书
国际金融专业自荐信
2014/07/05 职场文书
校外活动方案
2014/08/28 职场文书
晚自修旷课检讨书怎么写
2014/11/17 职场文书
大学生心理健康教育心得体会
2016/01/12 职场文书