本文是vue项目的js升级ts教程,也可以作为vue项目的ts使用教程。

项目背景:

  • vue版本:2.6.11
  • 基于vue-cli4脚手架生成
  • 已配置eslint(配置教程参考:传送门

一、安装依赖

vue ts基础:

npm i -D typescript @vue/cli-plugin-typescript

ts校验相关:

npm i -D @typescript-eslint/eslint-plugin @typescript-eslint/parser @vue/eslint-config-typescript eslint-plugin-import eslint-plugin-node eslint-plugin-promise eslint-plugin-standard

vue ts辅助:

npm i -D vue-class-component vue-property-decorator

二、添加文件

1、src目录添加两个ts声明文件,用于支持ts的类型声明:

shims-tsx.d.ts

import Vue, { VNode } from 'vue'

declare global {
  namespace JSX {
    // eslint-disable-next-line @typescript-eslint/no-empty-interface
    interface Element extends VNode {}
    // eslint-disable-next-line @typescript-eslint/no-empty-interface
    interface ElementClass extends Vue {}
    interface IntrinsicElements {
      [elem: string]: any;
    }
  }

  interface Window {
    __wxjs_environment: any;
    WebViewJavascriptBridge: any;
    _czc: any;
  }
}

shims-vue.d.ts

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

2、根目录添加tsconfig.json,用于项目的ts校验

{
  "compilerOptions": {
    "target": "esnext",
    "module": "esnext",
    "strict": true,
    "jsx": "preserve",
    "importHelpers": true,
    "moduleResolution": "node",
    "experimentalDecorators": true,
    "skipLibCheck": true,
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true,
    "sourceMap": true,
    "baseUrl": ".",
    "types": [
      "webpack-env"
    ],
    "paths": {
      "@/*": ["src/*"]
    },
    "lib": [
      "esnext",
      "dom",
      "dom.iterable",
      "scripthost"
    ]
  },
  "include": [
    "src/**/*.ts",
    "src/**/*.tsx",
    "src/**/*.vue",
  ],
  "exclude": [
    "node_modules",
    "dist",
    "public",
    "build"
  ]
}

三、使用方式

vue-property-decorator插件使用文档:传送门

1、script标签

要在.vue文件里使用ts,首先要在script标签上添加属性lang=“ts”
例如一个基础的App.vue

<template>
  <div id="app">
    <router-view/>
  </div>
</template>

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

@Component
export default class App extends Vue {

}
</script>

<style lang="less">
</style>

  • 导出的class类名等同js版导出的name属性

2、data数据

直接用等于号赋值

// ========== js版 ==========
data () {
 return {
	a: 1,
	b: false
 }
}

// ========== ts版 ==========
a = 1
b = false

3、computed计算属性

用get的形式

// ========== js版 ==========
computed: {
 aa () {
  return this.a
 }
}

// ========== ts版 ==========
get aa () {
 return this.a
}

4、生命周期钩子函数

保持不变,只是不同函数之间不需要用逗号分隔

5、methods方法

不再需要methods属性包裹,里面的方法直接提出来,和声明钩子函数平级(注意方法名别和钩子函数重名了),方法之间也不需要逗号分隔。

// ========== js版 ==========
created () {},

methods: {
  getData () {},
  setData () {}
}

// ========== ts版 ==========
created () {}

getData () {}
setData () {}

6、自定义组件components、自定义指令directives、过滤器filters

全都写在开头的@Component里

// ========== ts版 ==========
@Component({
  components: {},
  directives: {},
  filters: {}
})

7、props参数接收

通过@Prop定义

// ========== js版 ==========
props: {
  title: {
    type: String,
    default: ''
  },
  list: {
    type: Array,
    default () {
      return []
    }
}

// ========== ts版 ==========
@Prop({ default: '' }) readonly title!: string
@Prop({ default: () => [] }) readonly list!: any[]

8、watch监听

通过@Watch定义

// ========== js版 ==========
watch: {
  // 普通监听
  list (val, oldVal) {
  	console.log(val, oldVal)
  },
  // 深度监听
  res: {
  	handler () {
  	  console.log(val, oldVal)
  	},
  	deep: true,
  	immediate: false
  }
}

// ========== ts版 ==========
@Watch('list')
onWatchList (val, oldVal) { // 方法名没要求,但是要放在相应的@Watch下面,一一对应
  console.log(val, oldVal)
}

@Watch('res', { deep: true, immediate: false })
onHandleRes (val, oldVal) {
  console.log(val, oldVal)
}

9、computed的get set 版

通过@PropSync定义

// ========== js版 ==========
props: {
  // 父组件通过isShow.sync传递
  isShow: {
    type: boolean,
    default: false
  }
},

computed: {
  isShowModal: {
  	get () {
	  return this.isShow
	},
	set (val) {
	  this.$emit('isShow:update', val)
	}
  }
}

// ========== ts版 ==========
@PropSync('isShow', { default: false }) isShowModal!: boolean

10、mixins混入

直接上双混入:

  • js版
// mixins1.js
export default {
  created () {
    console.log(1)
  }
}

// mixins2.js
export default {
  created () {
    console.log(2)
  }
}

// index.vue
<template>
  <div id="index"></div>
</template>

<script>
import mixins1 from './mixins1'
import mixins2 from './mixins2'

export default {
  mixins: [mixins1, mixins2]
}
</script>
  • ts版
// mixins1.ts
import { Component, Vue } from 'vue-property-decorator'

@Component
export default class mixins1 extends Vue {
  created () {
    console.log(1)
  }
}

// mixins2.ts
import { Component, Vue } from 'vue-property-decorator'

@Component
export default class mixins2 extends Vue {
  created () {
    console.log(2)
  }
}

// index.vue
<template>
  <div id="index"></div>
</template>

<script lang="ts">
import { Component, Prop, Mixins } from 'vue-property-decorator'
import mixins1 from './mixins1'
import mixins2 from './mixins2'

@Component
export default class Index extends Mixins(mixins1, mixins2) {

}
</script>

11、vuex

推荐使用插件vuex-class 官方文档

npm i vuex-class -S

以带命名空间namespace的为例:

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

const userModule = namespace('user')

@Component
export default class Index extends Vue {
  @userModule.Getter isLogin!: boolean // 使用this.isLogin指代vuex的user模块里getters的同名isLogin
}

四、报错处理

1、引入第三方插件报无法找到模块

需要在shims-vue.d.ts文件里声明。

  • 例如: import md5 from ‘js-md5’,
    报错信息:无法找到模块"js-md5"的声明文件
    解决方法:在shims-vue.d.ts里添加
declare module 'js-md5' {
  import md5 from 'js-md5'
  export default md5
}

2、this.$ 报错 does not exist on type

常见于给vue实例添加的自定义属性上,比如 $toast $http $loading

  • 例如:this.$loading.show()
    报错信息:Property ‘loading’ does not exist on type ‘Index’.
    解决方法:shims-tsx.d.ts里添加以下声明(以 $loading为例)
declare module 'vue/types/vue' {
  interface Vue {
    $loading: { show: (text?: string) => void; hide: () => void }; // 具体类型以你自己的为准,或者直接用any
  }
}

3、引入路径带@时报错

报错信息:找不到模块…或其相应的类型声明。
可能的原因有很多,按照以下方法依次排查处理:

(1)配置tsconfig.json
  • 检查项目根目录tsconfig.json文件的path属性是否正确配置了@的路径
"paths": {
  "@/*": ["src/*"]
},
  • 如果@路径名是动态的,解决方法: shims-vue.d.ts里添加模块声明。
    例如 import api from ‘@/utils/api’ 报错,就按下面方式添加:
declare module '@/utils/api' {
  import api from '@/utils/api'
  export default api
}
(2).ts文件路径不能带后缀名,.vue文件路径需要加上后缀名

ts里引入.ts文件时路径不要加后缀名,引入.vue文件时路径需要带上.vue后缀名

import apis from '@/utils/api'
import ruleModal from '@/components/rule.vue'

4、其他各种疑难杂症

那就试试vscode工作区的配置:

  • 将项目文件夹拖进vscode里,以工作区的形式展示,然后将该项目拖到顶部,使其排在工作区的第一位,如图所示的dyd-h5项目,很多莫名其妙的问题都能通过这种方式解决:

在这里插入图片描述

五、结语

vue 2.x的版本需要引入第三方插件使用class类的形式才能较好的配合使用ts,写起来还是比较繁琐的,而已经发布了正式版的vue 3.0对ts的支持比较完全,而且可以使用function形式使用ts,类似于react的hooks,function形式更加简单方便,如果是新开的项目还是推荐直接上vue 3.0吧。

六、附录

1、.eslintrc.js

module.exports = {
  root: true,
  env: {
    browser: true,
    node: true
  },
  extends: [
    'plugin:vue/essential',
    '@vue/standard',
    '@vue/typescript/recommended'
  ],
  parserOptions: {
    ecmaVersion: 2020
  },
  globals: {
    _czc: true
  },
  rules: {
    'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off',
    'camelcase': 'off',
    'comma-dangle': 'off',
    '@typescript-eslint/no-explicit-any': 'off',
    '@typescript-eslint/camelcase': 'off',
    '@typescript-eslint/no-empty-function': 'off',
    'lines-between-class-members': 'off',
    '@typescript-eslint/no-this-alias': [
      "error",
      {
        "allowDestructuring": true,
        "allowedNames": ["that", "self"]
      }
    ]
  },
}
Logo

前往低代码交流专区

更多推荐