vue如何与TypeScript搭配使用

最近真的陷入了TypeScript无法自拔,我甚至有一种感觉ts可能会成为前端必须要掌握的一项技术(就像现在前端从业者必须得掌握三大框架之一一样)。

但现在使用vue+TypeScript来开发项目的其实还是比较少的,Vue的作者尤雨溪在知乎上回答过这么一个问题TypeScript 不适合在 vue 业务开发中使用吗?,根据他本人的回答,现在的vue2.x版本对TS的支持是远不如React和Angular的,所以在即将发布的vue3.0要加强这一块。

但是因为vue-class-componentvue-property-decorator(前者的升级版本,加入了Vue,Watch,Emit等功能)的存在,现在使用TS+vue来写项目也不是什么问题,虽然还有些稍许的bug,不过无伤大雅

在前面的博客里我介绍过如何使用TypeScript如何搭建store文件夹,所以这篇博文主要做一个用JS与TS编写vue文件的区别。(引入的是vue-property-decorator,全部介绍完基本上是不可能的,这里结束的是平时用的多的)

#data,computed,methods

JS写法

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
export default {
name:'HelloWorld',
data(){
return {
bar:'hello',
foo:1
}
},
computed:{
comBar(){
return this.bar+'world'
}
comFoo:{
get:function(){
return this.foo+1
}
set:function(newVal){
console.log(newVal)
}
}
},
methods:{
handleFn(){
// ...do something
}
}
}

TS写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import {Component,Vue} from 'vue-property-decorator';
@Component({
name:'HelloWorld'
})

export default class HelloWorld extends Vue {
bar:string = 'hello';
foo:number = 1;
get comBar():string{
return this.bar + 'world'
}
set comBar(val:string){
// 注意,这里不能标返回值类型,就算写void也不行
console.log(val)
}
handleFn():voids{
//...do something
}
}

@Component({options}) 中接受的options就是传统的配置,mixin,自定义指令,子组件的注册都要在这里声明

#watch

JS写法

1
2
3
4
5
6
7
8
9
10
11
export default {
watch:{
'foo':{
handler:function(newVal,oldVal){
console.log(newVal,oldVal)
},
deep:true, // 开启深度监听
immediate:true // 该回调(handler函数)将会在侦听开始之后被立即调用
}
}
}

TS写法

1
2
3
4
5
6
7
8
import {Vue,Watch,Component} from 'vue-property-decorator'
export default class HelloWorld extends Vue {
@Watch('foo',{deep:true,immediate:true})
onWatchFoo(newVal:string,oldVal:string){
// 监听的方法名是可以自定义的
// do something...
}
}

#props #directives

JS写法

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
export default {
name:'childComponent',
props:{
bar:Boolean,
foo:{
type:String,
defalut:'abc'
},
obj:{
type:Object,
default:function(){
return {
a:1
}
},
required:true // 定义该prop是否是必填项
validator:function(value){
// 自定义验证函数 return出来一个boolean值
return Object.keys(values).includes('a')
}
}
},
directives:{
focus:{
inserted:function(el,binding,vnode,oldVnode){
// el:指令所绑定的元素,可以用来直接操作DOM
el.focus();
// binding:一个对象,包括以下属性
// name: 指令名--focus
// value: 指令的绑定值,例如v-my-directives="1+1"中,value的值为2
// oldValue:指令绑定的前一个值。仅在update和componentUpdated钩子中可用。无论值是否改变都可用
// expression:字符串形式的指令表达式。例如v-my-directives="1+1"中,表达式为‘1+1’
// arg:传给指令的参数,例如 v-my-directives:foo='...'中,参数为‘foo’
// modifiers:一个包含修饰符的对象。例如:v-my-directives.stop.prevent中,修饰符对象为{stop:true,prevent:true}
let {name,value,oldValue,expression,arg,modifiers} = binding;
// vnode: Vue 编译生成的虚拟节点
// oldVnode:上一个虚拟节点,仅在update和componentUpdated钩子中使用
}
}
}
}

TS写法

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
import {Vue,Component,Prop} from 'vue-property-decorator';
// 第一种你可以在在@Component里
@Component({
props:{
bar:{
type:Boolean,
default:false
}
},
directives:{
focus:{
inserted:function(el,binding,vnode,oldvnode){
el.focus()
}
}
}
})
// 第二种使用@Prop的方式
interface ObjProp {
a:number
}
@Component()
export default class HelloWorld extends Vue {
@Prop({default:false})
bar!:boolean //!: 表示强制解析,告诉ts我这里一定有值 如果没有写!,虽然不会报错但是代码会有红线提示
@Prop(
{
default:function(){
return {msg:'haha3'}
},
required:true,
validator:function(value){
return Object.keys(value).includes('a')
}
}
)
obj!:ObjProp
// 好像直接写一个{}作为默认值也没问题
obj!:{
a:number
}
}

@Model

前提条件,在父组件中,对子组件使用了v-mode="checked"

父组件

1
<checkbox v-mode="checked" ></checkbox>

子组件

1
<input type="checkbox" :checked="checked" @change="change"/>

JS写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// 子组件中
export defalut {
// model 参数为2.2.0新增,允许一个自定义组件在使用v-model时定制prop和event
model:{
prop:"checked",
event:"change"
},
props:{
checked:Boolean
},
methods:{
change:function(e){
this.$emit('change',e.target.checked)
}
}
}

TS写法

1
2
3
4
5
6
7
8
import {Vue,Component,Model,Emit} from "vue-property-decorator";
@Component()
export default class HelloWorld extends Vue {
@Model('checked',{type:Boolean})
checked!:boolean,
@Emit('change')
change(e:MouseEvent)
}

@Emit

父组件

1
<add-component @on-add="addFunction"></add-component>

有一个可以做加减法的子组件,如下

1
2
3
4
5
<div>
<button @click="onSub">-</button>
<input tepe="test" v-model="count"/>
<button @click="onAdd">+</button>
</div>

JS写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
export default {
data(){
return {
count:0
}
},
name:'addcomponent',
methods:{
onSub(){
this.$emit('on-sub',this.count)
},
onAdd(){
this.$emit('on-add',this.count)
}
}
}

TS写法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
import {Vue,Component,Emit} from 'vue-property-decorator';
@Component({
name:'addcomponent'
})
export default class AddComponent extends Vue {
count:numer = 0;
@Emit() // 如果父组件传过来的方法是 on-add 那么,@Emit里可以省略方法名
onAdd(){
this.count += 1;
return this.count // 通过return的方式,在父组件的方法中才能在event中拿到值
}

@Emit('otherFnName')
onSub(){
this.count -=1;
return this.count
}
}

@Provide @Inject

通常provideinject是配合使用的,一般使用在插件与组件库中,在父组件中通过provide提供一些数据,他的所有子组件,都能都通过inject注入到自己的组件中,并且通过this来调用。

provide:Object | () => Object
inject:Array<string> | { [key: string]: string | Symbol | Object }

JS用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 父组件
export default {
name:"father-component",
provide:{
bar:1,
foo:2
}
}
...
// 任意子组件
export default {
inject:['bar','foo']
// 如果需要定义默认值
inject:{
bar:{
default:'heihei'
},
foo1:{
form:'foo', // 声明从不同名字的属性注入
defalut:'haha' // 非原始属性,已工厂方法返回 ()=>[1,2,3]
}
}
}

TS用法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 父组件
import {Vue,Component,Provide} from 'vue-property-decorator';
@Component
export default class Father extends Vue {
@Provide()
foo = "hello vue"
}

// 子组件
import {Vue,Component,Inject} from 'vue-property-decorator';
@Component
export default class Child extends Vue {
@Inject()
foo !:string
}

综上所写,使用TS与使用JS编写vue文件的区别就全部罗列完了

但应该还有很多遗漏的点,如果你有什么补充,欢迎在留言区评论。

我想吃鸡腿!