在Vue2中使用TypeScript的方法总结

一、安装插件

在Dependencies中安装的插件:

  • vue-class-component 通过装饰器模式实现vue对ts的适配
  • vue-property-decorator 对vue-class-component的增强,可以单独使用
  • vuex-module-decorator 增加对vuex的支持

在DevDependencies中安装的插件:

  • vue-tsx-support 支持tsx,实现如同react的props组件的智能提示。
  • typescript TS的解释器

二、JS与TS在Vue2中的对比

1. 组件实例

使用@component来定义组件

1
2
3
4
5
6
import { Vue, Component } from 'vue-property-decorator'

@Component
export default class Index extends Vue {

}

相当于

1
2
3
4
5
<script>
module.export = {

}
</script>

2. 生命周期

跟原生的差不多,就是直接定义方法

1
2
3
4
5
6
7
8
9
10
11
12
import { Vue, Component } from 'vue-property-decorator'

@Component
export default class Index extends Vue {
created() {
console.log('created')
}

mounted() {
console.log('mounted')
}
}

相当于

1
2
3
4
5
6
7
8
9
10
<script>
module.export = {
created() {
console.log('created')
},
mounted() {
console.log('mounted')
}
}
</script>

3. 响应式数据data

变量定义跟原生最大的区别就是TS虽然也有类型推断,但一般会手动定义每个变量的类型
另外值得注意的是,如果不给变量赋初始值,或者赋值为undefined,这个变量在vue-class-component中是不具备相应是的。
因此如果希望变量具备响应式,最起码要赋值为null

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import { Vue, Component } from 'vue-property-decorator'

type User = {
name: string,
age: number
}

@Component
export default class Index extends Vue {
message = 'hello world'
info: User = { name: 'test', age: 25 }
// 这个变量不具备响应式,要赋值为null才具备响应式
count: number
}

相当于

1
2
3
4
5
6
7
8
9
10
<script>
module.export = {
data() {
return {
message: 'hello world',
info : { name: 'test', age: 25 }
}
}
}
</script>

4. 计算属性computed

直接使用get就可以定义computed

1
2
3
4
5
6
7
8
import { Vue, Component } from 'vue-property-decorator'

@Component
export default class Index extends Vue {
get introduction() {
return `姓名:${this.info.name},年龄:${this.info.age}`
}
}

相当于

1
2
3
4
5
6
7
8
9
<script>
module.export = {
computed: {
introduction() {
return `姓名:${this.info.name},年龄:${this.info.age}`
}
}
}
</script>

5. 数据监听watch

使用@Watch来装饰

1
2
3
4
5
6
7
8
9
import { Vue, Component, Watch } from 'vue-property-decorator'

@Component
export default class Index extends Vue {
@Watch('$route', { immediate: true })
changeRoute(newVal: Route, oldVal: Route) {
console.log('$route watcher:', newVal, oldVal)
}
}

相当于

1
2
3
4
5
6
7
8
9
10
11
12
<script>
module.export = {
watch: {
route: {
handler(newVal, oldVal) {
console.log('$route watcher:', newVal, oldVal)
},
immediate: true
}
}
}
</script>

6. 方法methods

正常的函数定义

1
2
3
4
5
6
7
8
import { Vue, Component } from 'vue-property-decorator'

@Component
export default class Index extends Vue {
hello() {
console.log('hello world')
}
}

相当于

1
2
3
4
5
6
7
8
9
<script>
module.export = {
methods: {
hello() {
console.log('hello world')
}
}
}
</script>

7. 引入组件

跟原生一样,使用之前要注册一下:

1
2
3
4
5
6
7
8
9
10
import { Vue, Component } from 'vue-property-decorator'


@Component({
components: {
Header
}
})
export default class Index extends Vue {
}

相当于

1
2
3
4
5
6
7
<script>
module.export = {
components: {
Header
}
}
</script>

8. 组件属性props

使用@props装饰属性

1
2
3
4
5
6
7
8
9
import { Vue, Component, Prop } from 'vue-property-decorator'
import { User } from '@/types/one'

@Component
export default class Header extends Vue {
@Prop({ type: String, default: '标题' }) readonly title?: string;
// 复杂的数据类型需要使用箭头函数返回
@Prop({ type: Object, default: () => ({ name: '-', age: '-' }) }) readonly auther!: User
}

相当于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<script>
module.export = {
props: {
title: {
type: String,
required: false,
default: '标题'
},
auther: {
type: Object,
required: true,
default: { name: '-', age: '-' }
}
}
}
</script>

9. 事件触发

跟methods的定义一样

10. ref使用

使用@Ref装饰器进行包装

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<template>
<div class="container">
<Header ref="header" title="首页" :author="info" />
</div>
</template>

<script lang="ts">
import { Vue, Component, Ref } from 'vue-property-decorator'
import { Header } from '../component/header/index.vue'

@Component({
components: {
Header
}
})

export default class Index extends Vue {
@Ref('header') readonly headerRef: Header
}
</script>

相当于

1
2
3
4
5
6
7
8
9
10
11
12
<script>
export default {
computed() {
headerRef: {
cache: false,
get() {
return this.$refs.header as Header
}
}
}
}
</script>

11. mixins使用

首先定义一个Vue实例,然后用Mixins实现继承
先定义一个Vue组件实例:

1
2
3
4
5
@Component
export class Hello extends Vue {
mixinText = 'Hello mixins'
obj: { name: string } = { name: 'han' }
}

接着在另一个组件中使用它

1
2
3
4
5
6
7
8
9
<script lang='ts'>
import { Component, Mixins, Watch, Ref } from 'vue-property-decorator'
@Component
export default class Index extends Mixins(Hello) {
created() {
console.log(this.mixinText, this.obj.name)
}
}
</script>

这里其实就是继承了Mixins(Hello),而这个Mixins(Hello)猜测大致作用就是将内部的变量跟函数挂到this上
以上代码相当于Vue2中的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<script>
export default {
mixins: {
data() {
return {
mixinText: 'Hello mixin',
obj: { name: 'han' }
}
}
},
created() {
console.log(this.mixinText, this.obj.name)
}
}
</script>

12. slots和scopedSlots的使用

这个主要是在HTML中定义,所以使用方式跟原生的差不多

13. emit向父组件发送数据

使用@Emit来装饰emit功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { Vue, Component, Emit } from 'vue-property-decorator'

export default class Index extends Vue {
count = 0

@Emit()
addToCount(n: number) {
this.count += n
}

@Emit('reset')
resetCount() {
this.count = 0
}

@Emit()
returnValue() {
return 10
}

@Emit()
onInputChange(e) {
return e.target.value
}

@Emit()
promise() {
return new Promise(resolve => {
setTimeout(() => {
resolve(20)
}, 0)
})
}
}

相当于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
export default {
data() {
return {
count: 0
}
},
methods: {
addToCount(n) {
this.count += n
this.$emit('add-to-count', n)
},
resetCount() {
this.count = 0
this.$emit('reset')
},
returnValue() {
this.$emit('return-value', 10)
},
onInputChange(e) {
this.$emit('on-input-chaneg', e.target.value, e)
},
promise() {
const promise = new Promise(resolve => {
setTimeout(() => {
resolve(20)
}, 0)
})

promise.then(value => {
this.$emit('promise', value)
})
}
}
}

四、vue-router

vue-router这个功能在使用vue-cli创建TS项目的时候就可以附加上,无需额外安装。
其配置基本跟原生一样,就是定一个routes对象用来保存路由路径,然后new一个VueRouter,将routes传入给VueRouter

而不同的地方在于需要注册路由钩子函数才能使用导航守卫,比如下面这样:

1
2
3
4
// class-component-hook.js
import Component from 'vue-class-component'

Component.registerHooks(['beforeRouteEnter', 'beforeRouteLeave', 'beforeRouteUpdate'])

然后给Vue类型进行拓展定义

1
2
3
4
5
6
7
8
9
10
import Vue from 'vue'
import { Route, NavigationGuardNext } from 'vue-router'

declare module 'vue/types/vue' {
interface Vue {
beforeRouteEnter?(to: Route, from: Route, next: NavigationGuardNext<Vue>): void
beforeRouteLeave?(to: Route, from: Route, next: NavigationGuardNext<Vue>): void
beforeRouteUpdate?(to: Route, from: Route, next: NavigationGuardNext<Vue>): void
}
}

最后在入口文件中引入这个class-component-hook.js,即可在使用导航守卫的时候得到准确的提示。

五、使用Vuex

在ts中可以借助vuex-module-decorator插件来使用vuex。

1. 模块创建

主要使用VuexModule作为模块父类
使用getModule导出模块
使用Module、Mutation、Action装饰

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
import { VuexModule, Module, Mutation, Action, getModule } from 'vuex-module-decorators'
import store from './index'

// 定义TodoItem结构
type TodoItem = {
id: string
content: string
isDone: boolean
}

// 定义接口
type TodoListState = {
todos: TodoItem[]
}

// 定义假数据
const todos: TodoItem[] = [
{
id: '0',
content: 'todo-item1',
isDone: false
},
{
id: '1',
content: 'todo-item2',
isDone: true
},
{
id: '2',
content: 'todo-item3',
isDone: false
}
]

// 使用装饰器@Module,设置模块为动态,并设置模块名称
@Module({ dynamic: true, store, name: 'todoListModule' })
class TodoListModule extends VuexModule implements TodoListState {
todos: TodoItem[]

@Action
async getAllTodoItems() {
const data = await new Promise<TodoListItem[]>(resolve => {
setTimeout(resolve, 100, todos)
})
this._saveTodos(data)
}

@Mutation
private _saveTodos(data: TodoItem[]) {
this.todos = data
}
}

export default getModule(TodoListModule)

2. store的创建和使用

1
2
3
4
5
6
7
8
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

// 先定义一个空的,一会再动态引入模块
const Store = new Vuex.Store<{}>({})
export default Store

3. 动态导入模块

因为state在原生Vue中是通过计算属性引入的,所以这里使用get

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { Component, Vue } from 'vue-property-decorator'
import TodoListModule from '@/modules/todoList'

@Component
export default class Index extends Vue {
get todos() {
return TodoListModule.todos
}

created() {
TodoListModule.getAllTodoItems.then(() => {
console.log('todos', this.todos)
})
}
}

参考:

typescript在vue2项目中的使用方法
在 vue2 中使用 ts
vue-class-component官方文档
vuex-module-decorator的gtihub
vue-tsx-support的github
阿里出品的pont库
vue-property-decorator的文档


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!