一、安装插件
在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来定义组件
| 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 } 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'
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({ 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的文档