使用 typescript 构建 Vue 应用
一、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 mutation
public say() {console.log("say1");
}
@Action("speak") // 在 speak()方法前修饰,当 speak()方法执行的时候就会提交一个 speak action
public speak(){}
public mounted () {this.say();
this.speak();}
}