一、vue项目初始化-引入typescript
使用typescript构建vue应用和使用js一样,都是通过vue-cli去初始化并创建一个vue项目,只不过使用typescript构建的时候要在脚手架问卷操作的时候勾选上typescript选项。
二、typescript Vue项目比较
使用typescript构建的Vue项目发生了一些变化:
① main.js变成了main.ts,但是main.ts中的内容和main.js的内容是一模一样的。
② router.js变成了router.ts,但是router.ts中的内容和router.js中的内容也是一模一样的。
③ store.js变成了store.ts,但是store.ts中的内容和store.ts中的内容也是一模一样的。
因为typescript是JavaScript的超集,所以 ts完全兼容js。
④ 新增了一个shims-vue.d.ts声明文件,这个文件的作用就是让typescript能够识别.vue文件,在引入"*.vue"文件的时候,会将其标识为一个Vue组件,才能对.vue文件进行类型校验,其具体内容如下:
declare module '*.vue' {import Vue from 'vue'export default Vue
}
⑤ 新增了一个shims-tsx.d.ts文件,其作用就是为了能够解析.tsx文件,对.tsx文件进行类型校验,其具体内容如下:
import Vue, { VNode } from 'vue'
declare global {namespace JSX {interface Element extends VNode {}interface ElementClass extends Vue {}interface IntrinsicElements {[elem: string]: any}}
}
⑥ 当然还会新增一个ts.config文件,这个是typescript的配置文件,具体内容这里不作解释,请参考tsconfig文件详解
三、.vue文件内容格式与写法
使用typescript来写Vue应用,最主要的就是.vue文件,.vue文件写法上与js有些不同并且 新增了一些装饰器,接着一步一步分析。
① 虽然.vue文件的格式和写法上有了不同,但这不同只是<script></script>部分发生了变化,<template></template>和<style></style>和原来是一样的,一个最简单.vue文件仍然可以使用如下写法:
// HelloWorld.vue
<template><div>hello world</div>
</template>
② 在写<script></script>部分,第一点不同就是,<script>标签上要加上lang语言属性,表示其中的内容为ts,如:
<script lang="ts">
</script>
③ 默认export上的不同,使用js的时候,我们是直接通过export default {}导出一个Vue组件对象即可,但是使用ts的时候,我们必须导出一个类class,类名为组件名,同时这个类必须继承Vue,如:
// import Vue from "vue";
import { Component, Vue } from 'vue-property-decorator'; // 引入Vue及一些装饰器
@Component
export default class App extends Vue { // 继承Vue并导出Vue组件}
以上就是<sciprt>内容最基本的写法,上面继承的Vue不是直接从"vue"模块中引入,而是从"vue-property-decorator"即vue属性装饰器模块中引入,当然 也可以通过vue模块引入import Vue from "vue",但是我们写.vue文件的时候 通常要引入一些装饰器,同时 这个装饰器类vue-property-decorator也提供了Vue,故可以直接从vue-property-decorator装饰器类上直接引入
④ 组件中data属性的写法,由于我们在.vue文件中声明了一个class,这个class就是Vue组件,我们可以直接在这个class中声明属性即可,这些声明的属性就是之前使用js写时的data属性中的数据,如:
export default class App extends Vue {public lists = [ // 这里就是之前Vue组件的data属性"Vue.js", "react.js", "Angular.js"]
}
⑤ 组件中的computed计算属性的写法,同样我们可以在class中声明get和set方法即可变成对应的computed属性,如:
export default class App extends Vue {public lists = [ // 这里就是之前Vue组件的data属性"Vue.js", "react.js", "Angular.js"]public get count() { // 通过get和set实现计算属性return this.lists.length;}
}
⑥ 组件中方法的声明更简单,直接在class中声明方法即可,如:
export default class App extends Vue {public say() {console.log("say");}
}
⑦ @Component装饰器的使用,@Component装饰器就是用来标识当前这个类是一个Vue组件,@Component装饰器还可以传递对象作为参数,这个传递的对象就是Vue组件实例,所以所有之前用js写法的时候,Vue组件支持的所有选项都可以传入,如:
@Component({// 这里可以配置Vue组件支持的各种选项components: {HelloWorld},data() {return {a:1}},methods: {say(){console.log("say");}}
})
export default class App extends Vue {
}
@Component内容使用的是 Vue.extend()方法,用于扩展Vue Component并生成Vue组件,这里需要注意的就是, Vue最终会将class中定义的属性和@Component中定义的属性进行合并,如果二者中定义了同名的属性,那么 class中的优先级更高,即class中定义的会覆盖掉@Component中定义的同名属性,但是 data除外, @Component中定义的同名的data数据会覆盖掉class中定义的同名data属性,如:
@Component({data() {bar: 1},methods: {say(){console.log("say::@Component");}}
})
export default class App extends Vue {public bar: number = 2; // 这里的bar会被@Component中定义的bar覆盖掉public say() { // 这里的say()方法会覆盖掉@Component中定义的say方法console.log("say::class");}
}
还有一点要注意的是, @Component装饰器千万不要漏写,必须在组件类class前修饰
⑧ props属性的写法,使用ts写Vue组件的时候,如果要在组件上定义props属性,那么必须通过@Prop()装饰器,其实就是在定义组件data属性的时候用@Prop()装饰器进行修饰,如果没有通过@Propp()进行修饰,那么定义的属性就是组件的data属性,可以给@Prop()传递一个配置对象,可以定义传递属性的default、required、type等属性,如:
import { Component, Vue, Prop } from 'vue-property-decorator';
@Component // 不要忘了用@Component修饰组件class哟
export default class HelloWorld extends Vue {@Prop() public bar!: string; // 这里定义的是props属性public foo = "foo"; // 这里定义的是data属性@Prop({type: String, default:"foo", required: true}) public foo!: string;
}
</script>
需要注意的是,public foo!: string, 这里声明的string类型是不起作用的,要限定父组件传入的foo属性的数据类型, 必须在@Prop()装饰器内限定数据类型,否则无效
⑨ @Emit()装饰器的使用,在子组件发射自定义事件的时候通常会通过this.$emit("say")的方式,但是typescript提供了一个@Emit()装饰器,用于修饰一个方法,当这个方法被执行的时候,就会在方法执行完成后发射一个同方法名的事件出去,当然,这是在没有给@Emit()传递参数的情况下,如果给@Emit()传递了参数,那么就会发射指定参数的事件,如:
export default class HelloWorld extends Vue {@Emit() // 在say()方法执行完成后会发射一个同方法名的say事件public say() {}@Emit("speak") // 这里给@Emit()传递了参数,则会发射speak事件public say() {}
}
⑩ @Watch()装饰器的使用,其拥有监听组件中数据的变化,就相当于$watch,需要给其传递一个字符串参数,表示其监听的是哪个数据的变化,如:
@Watch("foo") // 监听this.foo的变化
public add(newValue: number, oldValue: number) {console.log(`newValue is ${newValue}, oldValue is ${oldValue}`);
}
四、vuex中的变化
vuex中的数据是存放在state属性上的,如果要限定state中数据的属性和类型,那么我们必须在创建store对象的时候定义一个接口限定一下数据类型,如:
interface IState {lists: string[]
}
export default new Vuex.Store<IState>({state: {lists: ["vue"] // 定义了state的数据结构,必须要有lists属性,并且属性值为string[]}
});
获取vuex中的数据时候, 还是可以通过this.$store.state.lists获取到,但是我们也可以通过装饰器获取到,要使用 vuex的装饰器,我们需要安装 vuex-class,如:
import {State, Mutation, Action} from "vuex-class";
export default class App extends Vue {// 将从vuex中获取到的lists数据保存到组件的lists属性上@State("lists") public lists!: string[];@Mutation("say") // 在say()方法前修饰,当say()方法执行的时候就会提交一个say mutationpublic say() {console.log("say1");}@Action("speak") // 在speak()方法前修饰,当speak()方法执行的时候就会提交一个speak actionpublic speak(){}public mounted () {this.say();this.speak();}
}
vue-class-component 是尤大大推出的vue对typescript支持的装饰器(库)vue
vue-class-component强调了几点用法:git
一、methods能够直接声明为类成员方法
二、computed属性能够声明为类属性访问器
三、data数据能够声明为类属性
四、data render 和全部Vue生命周期挂钩也能够直接声明为类成员方法,但不能在实例自己上调用它们。在声明自定义方法时,应避免使用这些保留名称。
五.使用方法
官网demo
<script>
import Vue from 'vue'
import Component from 'vue-class-component'@Component({props: {propMessage: String}
})
export default class App extends Vue {// 初始化数据 data能够声明成类属性形式msg = 123// 使用propshelloMsg = 'Hello, ' + this.propMessage// 生命周期钩子声明 保留名称mounted () {this.greet()}// computed属性能够声明成类方法形式get computedMsg () {return 'computed ' + this.msg}// method方法能够声明成类方法形式greet () {alert('greeting: ' + this.msg)}
}
</script>
复制代码
createDecorator
createDecorator能够自定义装饰器,须要接受一个回调函数做为第一个参数,包括如下几个参数github
options:对象参数, key ,paramsIndex
// decorators.js
import { createDecorator } from 'vue-class-component'export const NoCache = createDecorator((options, key) => {// component options should be passed to the callback// and update for the options object affect the componentoptions.computed[key].cache = false
})
import { NoCache } from './decorators'@Component
class MyComp extends Vue {// computed属性不会再被缓存@NoCacheget random () {return Math.random()}
}
复制代码
Component.registerHooks
自定义钩子vue-router
// class-component-hooks.js
import Component from 'vue-class-component'// 注册钩子name
Component.registerHooks(['beforeRouteEnter','beforeRouteLeave','beforeRouteUpdate' // for vue-router 2.2+
])
// MyComp.js
import Vue from 'vue'
import Component from 'vue-class-component'@Component
class MyComp extends Vue {beforeRouteEnter (to, from, next) {console.log('beforeRouteEnter')next() // needs to be called to confirm the navigation}beforeRouteLeave (to, from, next) {console.log('beforeRouteLeave')next() // needs to be called to confirm the navigation}
}
复制代码
箭头函数
this为undefined, 本来使用箭头函数的this实际上指向的是vue实例, 可是将箭头函数定义为类属性并在其中访问它,则它将不起做用,this指向的是vue实例的代理typescript
@Component
class MyComp extends Vue {foo = 123bar = () => {this.foo = 456}
}
只要将函数定义为方法,就会自动生成vue实例缓存
@Component
class MyComp extends Vue {foo = 123bar () {// Correctly update the expected property.this.foo = 456}
}
类属性的undefined没有作出处理
对于一些属性的初始值应该用null或者data钩子进行处理bash
@Component
class MyComp extends Vue {// 不会进行处理foo = undefined// 会进行处理bar = nulldata () {return {// 会进行处理baz: undefined}}
}
vue-property-decorator
这个组件完全依赖于vue-class-component
.它具备以下几个属性:
- @Component (完全继承于
vue-class-component
) - @Emit
- @Inject
- @Provice
- @Prop
- @Watch
- @Model
- Mixins (在
vue-class-component
中定义);
使用
当我们在vue
单文件中使用TypeScript
时,引入vue-property-decorator
之后,script
中的标签就变为这样:
<script lang="ts">import {Vue, Component} from 'vue-property-decorator';@Component({})export default class "组件名" extends Vue{ValA: string = "hello world";ValB: number = 1;}
</script>
等同于
<script lang="es6">import Vue from 'vue';export default {data(){return {ValA: 'hello world',ValB: 1}}}
</script>
- 总结: 对于
data
里的变量对顶,我们可以直接按ts
定义类变量的写法写就可以
那么如果是计算属性呢? 这就要用到getter
了.
<script lang="ts">import {Vue, Component} from 'vue-property-decorator';@Component({})export default class "组件名" extends Vue{get ValA(){return 1;}}
</script>
等同于
<script lang="es6">import Vue from 'vue';export default {computed: {ValA: function() {return 1;}}}
</script>
- 总结: 对于
Vue
中的计算属性,我们只需要将该计算属性名定义为一个函数,并在函数前加上get
关键字即可.
原本Vue
中的computed
里的每个计算属性都变成了在前缀添加get
的函数.
@Emit
关于Vue
中的事件的监听与触发,Vue
提供了两个函数$emit
和$on
.那么在vue-property-decorator
中如何使用呢?
这就需要用到vue-property-decorator
提供的@Emit
属性.
<script lang="ts">import {Vue, Component, Emit} from 'vue-property-decorator';@Component({})export default class "组件名" extends Vue{mounted(){this.$on('emit-todo', function(n) {console.log(n)})this.emitTodo('world');}@Emit()emitTodo(n: string){console.log('hello');}}
</script>
运行上面的代码会打印 'hello' 'world', 为什么呢? 让我们来看看它等同于什么
<script lang="es6">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);}}}
</script>
可以看到,在@Emit
装饰器的函数会在运行之后触发等同于其函数名(驼峰式会转为横杠式写法
)的事件, 并将其函数传递给$emit
.
如果我们想触发特定的事件呢,比如在emitTodo
下触发reset
事件:
<script lang="ts">import {Vue, Component, Emit} from 'vue-property-decorator';@Component({})export default class "组件名" extends Vue{@Emit('reset')emitTodo(n: string){}}
</script>
我们只需要给装饰器@Emit
传递一个事件名参数reset
,这样函数emitTodo
运行之后就会触发reset
事件.
- 总结:在
Vue
中我们是使用$emit
触发事件,使用vue-property-decorator
时,可以借助@Emit
装饰器来实现.@Emit
修饰的函数所接受的参数会在运行之后触发事件的时候传递过去.@Emit
触发事件有两种写法
@Emit()
不传参数,那么它触发的事件名就是它所修饰的函数名.@Emit(name: string)
,里面传递一个字符串,该字符串为要触发的事件名.
@Watch
我们可以利用vue-property-decorator
提供的@Watch
装饰器来替换Vue
中的watch
属性,以此来监听值的变化.
在Vue
中监听器的使用如下:
export default{watch: {'child': this.onChangeValue// 这种写法默认 `immediate`和`deep`为`false`,'person': {handler: 'onChangeValue',immediate: true,deep: true}},methods: {onChangeValue(newVal, oldVal){// todo...}}
}
那么我们如何使用@Watch
装饰器来改造它呢?
import {Vue, Component, Watch} from 'vue-property-decorator';@Watch('child')
onChangeValue(newVal: string, oldVal: string){// todo...
}@Watch('person', {immediate: true, deep: true})
onChangeValue(newVal: Person, oldVal: Person){// todo...
}
- 总结:
@Watch
使用非常简单,接受第一个参数为要监听的属性名 第二个属性为可选对象.@Watch
所装饰的函数即监听到属性变化之后的操作.
@Prop
我们在使用Vue
时有时会遇到子组件接收父组件传递来的参数.我们需要定义Prop
属性.
比如子组件从父组件接收三个属性propA
,propB
,propC
.
propA
类型为Number
propB
默认值为default value
propC
类型为String
或者Boolean
export default {props: {propA: {type: Number},propB: {default: 'default value'},propC: {type: [String, Boolean]},}
}
我们使用vue-property-decorator
提供的@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 value'}) propB!: string;@propC([String, Boolean]) propC: string | boolean;}
</script>
这里
!
和可选参数?
是相反的,!
告诉TypeScript
我这里一定有值.
- 总结:
@Prop
接受一个参数可以是类型变量或者对象或者数组.@Prop
接受的类型比如Number
是JavaScript
的类型,之后定义的属性类型则是TypeScript
的类型.
Mixins
在使用Vue
进行开发时我们经常要用到混合,结合TypeScript
之后我们有两种mixins
的方法.
一种是vue-class-component
提供的.
//定义要混合的类 mixins.ts
import Vue from 'vue';
import Component from 'vue-class-component';@Component // 一定要用Component修饰
export default class myMixins extends Vue {value: string = "Hello"
}
// 引入
import Component {mixins} from 'vue-class-component';
import myMixins from 'mixins.ts';@Component
export class myComponent extends mixins(myMixins) {// 直接extends myMinxins 也可以正常运行created(){console.log(this.value) // => Hello}
}
第二种方式是在@Component
中混入.
我们改造一下mixins.ts
,定义vue/type/vue
模块,实现Vue
接口
// mixins.ts
import { Vue, Component } from 'vue-property-decorator';declare module 'vue/types/vue' {interface Vue {value: string;}
}@Component
export default class myMixins extends Vue {value: string = 'Hello'
}
混入
import { Vue, Component, Prop } from 'vue-property-decorator';
import myMixins from '@static/js/mixins';@Component({mixins: [myMixins]
})
export default class myComponent extends Vue{created(){console.log(this.value) // => Hello}
}
- 总结: 两种方式不同的是在定义
mixins
时如果没有定义vue/type/vue
模块, 那么在混入的时候就要继承
该mixins
; 如果定义vue/type/vue
模块,在混入时可以在@Component
中mixins
直接混入.
@Model
Vue
组件提供model
: {prop?: string, event?: string}
让我们可以定制prop
和event
.
默认情况下,一个组件上的v-model
会把 value
用作 prop
且把 input
用作 event
,但是一些输入类型比如单选框和复选框按钮可能想使用 value prop
来达到不同的目的。使用model
选项可以回避这些情况产生的冲突。
- 下面是
Vue
官网的例子
Vue.component('my-checkbox', {model: {prop: 'checked',event: 'change'},props: {// this allows using the `value` prop for a different purposevalue: String,// use `checked` as the prop which take the place of `value`checked: {type: Number,default: 0}},// ...
})
<my-checkbox v-model="foo" value="some value"></my-checkbox>
上述代码相当于:
<my-checkbox:checked="foo"@change="val => { foo = val }"value="some value">
</my-checkbox>
即foo
双向绑定的是组件的checke
, 触发双向绑定数值的事件是change
使用vue-property-decorator
提供的@Model
改造上面的例子.
import { Vue, Component, Model} from 'vue-property-decorator';@Component
export class myCheck extends Vue{@Model ('change', {type: Boolean}) checked!: boolean;
}
总结, @Model()
接收两个参数, 第一个是event
值, 第二个是prop
的类型说明, 与@Prop
类似, 这里的类型要用JS
的. 后面在接着是prop
和在TS
下的类型说明.