搭建vue-cli时的配置:
打开项目,发现最大的不同是额外引入了一个库 vue-property-decorator
以及多了三个文件: shims-tsx.d.ts
、shims-vue.d.ts
和 tsconfig.json
。
shims-tsx.d.ts
允许你使用.tsx
结尾的文件,用来在 Vue 项目中使用 tsx 语法。 tsx 语法就是 jsx 语法的 typescript 版本。shims-vue.d.ts
默认情况下, typescript 不支持导入.vue
文件,这个文件让 typescript 识别出.vue
文件并让其按照VueConstructor<Vue>
的形式来处理。tsconfig.json
保存的是 typescript 编译器的配置,可以按照需求修改来达到想要的效果。- 由于
vue-property-decorator
的存在,我们可以使用基于类的注释装饰器进行开发。
vue 提供了两种使用 typescript 编写组件的方法,一个是使用 Vue.component
或 Vue.extend
定义组件。这种方式基本无学习成本,直接在现有的写法上结合 typescript 语法即可。但是这种方法不能完全实现 mixins 多混入的效果,只能混入一个。不推荐用这种方式写,无法实现多继承。
还有一种方式是 基于类的 Vue 组件
,学习成本较高,需要对类有一定的了解。主要用到 vue-property-decorator
,下面重点介绍这种方式:
vue-property-decorator 主要提供了多个装饰器和一个函数:
- @Prop
- @PropSync
- @Model
- @Watch
- @Provide
- @Inject
- @ProvideReactive
- @InjectReactive
- @Emit
- @Ref
- @Component(由 vue-class-component 提供)
- Mixins(由 vue-class-component 提供)
data
对于 data 里的变量定义,我们可以直接按 typescript 定义类变量的写法写就可以。
例子:
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
@Component({})
export default class "组件名" extends Vue {
name: string = "huangry";
age: number = 22;
}
</script>
computed
如果是计算属性,这就要用到 getter
了。对于 Vue 中的计算属性,我们只需要将该计算属性名定义为一个函数,并在函数前加上 get
关键字即可。原来 Vue 中的 computed
里的每个计算属性都变成了在前缀添加 get
的函数。
例子:
<script lang="ts">
import { Vue, Component } from 'vue-property-decorator';
@Component({})
export default class "组件名" extends Vue {
get ValA() {
return 1;
}
}
</script>
@Component
Component装饰器它注明了此类为一个 Vue 组件,因此即使没有设置选项也不能省略。如果需要定义比如 name、components、filters、directives以及自定义属性,就可以在Components装饰器中定义。
例子:
import { Vue, Component } from 'vue-property-decorator'
import MainHeader from './header.vue'
import MainContent from './content.vue'
@Component({
components: {
MainHeader,
MainContent
}
})
export default class Test extends Vue {}
@Prop
我们在使用 Vue 时有时会遇到子组件接收父组件传递来的参数,我们需要定义 Prop
属性。 @Prop
接受一个参数可以是类型变量或者对象或者数组。 @Prop
接受的类型比如 Number
是 JavaScript
的类型,之后定义的属性类型则是 TypeScript
的类型。
具体使用方法可以看下面例子:
export default {
props: {
propA: {
type: Number
},
propB: {
default: 'default value'
},
propC: {
type: [String, Boolean]
}
}
}
// 用 @Prop 实现上面的代码
<script lang="ts">
import { Vue, Component, Prop } from 'vue-property-decorator';
@Component({})
export default class "组件名" extends Vue {
// 注意要加非空断言符 ! ,不然会报错。
@Prop(Number)
propA!: number;
@Prop({default: 'default vale'})
propB!: string;
@Prop([String, Boolean])
propC!: string | boolean;
}
</script>
注意:这里 !
和可选参数 ?
是相反的, !
告诉 TypeScript
这里一定有值。
@Watch
我们可以利用 vue-property-decorator
提供的 @Watch
装饰器来替换 Vue
中的 watch
属性,以此来监听值的变化。@Watch
使用非常简单,接受第一个参数为要监听的属性名,第二个属性为可选对象。@Watch
所装饰的函数即监听到属性变化之后的操作。
举个例子来说明 @Watch
怎么用:
export default {
watch: {
'child': this.onChangeValue,
'person': {
handler: 'onChangeValue',
immediate: true,
deep: true
}
},
methods: {
onChangeValue(newVal, oldVal) {
// ...
}
}
}
// 在 vue-property-decorator 中 @Watch 的用法
import { Vue, Component, Watch } from 'vue-property-decorator';
@Watch('child')
onChangeValue(newVal: string, oldVal: string) {
// ...
}
@Watch('person', { immediate: true, deep: true })
onChangeValue(newVal: Person, oldVal: Person) {
// ...
}
@Emit
关于 Vue 中的事件的监听与触发, Vue 提供了两个函数 $emit
和 $on
。在 vue-property-decorator
中使用 @Emit
装饰器。
在 JavaScirpt 中如下:
import Vue from 'vue';
export default {
mounted() {
this.$on('emit-todo', function(n) {
console.log(n)
})
this.emitTodo('world');
},
methods: {
emitTodo(n) {
console.log('hello');
this.$emit('emit-todo', n);
}
}
}
在 TypeScript 中如下:
import { Vue, Component, Emit } from 'vue-property-decorator';
@Component({})
export default class Some extends Vue {
mounted() {
this.$on('emit-todo', function(n) {
console.log(n);
})
this.emitTodo('world');
}
@Emit()
emitTodo(n: string) {
console.log('hello');
}
}
在 @Emit
装饰器的函数会在运行之后触发等同于其函数名(驼峰式会转为横杆式写法)的事件,并将其函数传递给 $emit
。如果我们想触发特定的事件呢,比如在 emitTodo
下触发 reset
事件:
import { Vue, Component, Emit } from 'vue-property-decorator';
@Component({})
export default class "组件名" extends Vue {
@Emit('reset')
emitTodo(n: string) {
// ...
}
}
我们只需要给装饰器 @Emit
传递一个事件名参数 reset
,这样函数 emitTodo
运行之后就会触发 reset
事件。
@Emit
修饰的函数所接受的参数会在运行之后触发事件的时候传递过去。
@Emit
触发事件有两种写法:
@Emit
不传参数,那么它触发的事件名就是它所修饰的函数名。@Emit(name: string)
里面传递一个字符串,该字符串为要触发的事件名。
Mixins
vue-class-component 能够实现多混入,写法类似类继承。
// mixin1.ts
import Vue from 'vue'
export default Vue.extend({
data() {
return {
valFromMixin1: 'test'
}
}
})
// 不能是下面这种写法会报 Mixin1 is not a constructor function type
export default {
data() {
return {
valFromMixin1: 'test'
}
}
}
// mixin2.ts
import { Component, Vue } from 'vue-property-decorator'
@Component
export default class Mixin2 extends Vue {
methodFromMixin2() {}
}
// test.ts
import Mixin1 from './mixin1'
import Mixin2 from './mixin2'
import { Component, Mixins } from 'vue-property-decorator'
export default class Test extends Mixins(Mixin1, Mixin2) {
test() {
this.methodFromMixin2()
console.log(this.valFromMixin1)
}
}
// 如果只混入一个的话,可以这样写
export default class Test extends Mixin1 {}
export default class Test extends Mixin2 {}
这样写不仅不会报错,而且编辑器还有提示。
Vue Router
在 router/index.ts 内为变量添加相应 interface 即可:
在 vue-router 中 routes 的类型定义
类型:Array<RouteConfig>
declare type RouteConfig = {
path: string;
component?: Component;
name?: string; // for named routes (命名路由)
components?: { [name: string]: Component }; // for named views (命名视图组件)
redirect?: string | Location | Function;
alias?: string | Array<string>;
children?: Array<RouteConfig>; // for nested routes (嵌套路由)
beforeEnter?: (to: Route, from: Route, next: Function) => void;
meta?: any;
}
在 router/index.ts 里需要加的代码:
import Vue from 'vue';
import VueRouter, { RouteConfig } from 'vue-router';
import Layout from '@/views/Layout/index.vue';
Vue.use(VueRouter);
const routes: RouteConfig[] = [
{
path: "/",
name: "layout",
component: Layout
},
// ...其他routers
]
const router = new VueRouter({
routes
});
export default router;
如果像在组件内使用 Vue Router 导航钩子,必须注册一次:
import { Component } from "vue-property-decorator";
Component.registerHooks([
"beforeRouteEnter", // 进入路由之前
"beforeRouteLeave", // 离开路由之前
"beforeRouteUpdate"
]);
值得注意的是,目前 vue2.x 虽然对 TypeScript 的支持已经有了一定进步,但是整体的代码提示还是不到位,仍然存在一些问题。 vue3.0 要到2020年一季度发布,这个版本的 vue 只是个过渡,想要让 vue 更好的与 typescript 结合,我们可以期待 vue3.0 的到来。
参考文章: