国庆前夕,Vue.js 作者发布了关于 Vue.js v3.0 开发计划的文章,文章中有提到将会使用 TypeScript 重新编写 Vue.js 的源码,使得源码结构更清晰,更易维护。因此觉得是时候学习下 TypeScript 了。
对于 TypeScript 并不陌生,在前端的世界一直都有听说过这个单词,但是只知道它的一些大概的情况,比如说 TypeScript 是微软开发的语言,是 JavaScript 的超集,它包含了 JavaScript 的最新语法和采用类型检查(不然也不会叫 TypeScript)。但是,对于它的具体的语法却没什么了解,类型检查也有接触了一点,因为以前学过一点 Vue.js 的源码知识,知道 Vue.js v2.0 源码引用了和 TypeScript 一样的类型检查,是 Facebook 开发的 Flow 插件。
另外,还知道关于一些 Vue.js 和 TypeScript 结合的情况,网上都说 Vue.js 对于 TypeScript 的支持并不是特别的好。记得刚开始学习 Vue.js 的时候,在 Vue.js 的官方文档中就看到过,Vue.js 的兼容性是非常好的,不仅支持 ES5,ES6 还支持 TypeScript 的,甚至是 react 的 JSX 用法。不过用的最多的还是 ES5 语法。
然后自然而然的去看了下 TypeScript 的文档(手册指南),还看了一些关于 TypeScript 的学习 demo,也看了一些别人写的关于 TypeScript 的博客文章,貌似 TypeScript 和 Vue.js 的结合例子或者文章确实是比较少,而使用在另外两个框架(React 和 Angular)的例子会更多一些,相关的文章也更多一点。
学习了 TypeScript 之后,肯定是用动手练习一下才能巩固好知识的,以前也写过不少 Vue.js 的小 demo 和项目,是不是要找几个项目使用 TypeScript 重构一下?然后发现以前学习的高仿饿了么的项目可以派上用场,这个项目的第三方依赖主要是黄轶老师写的滚动库better-scroll,而现在这个库已经支持 TypeScript 了。
构建#
以前学习的高仿饿了么项目采用的 vue-cli v2 脚手架构建的,现在 vue-cli v3 已经出来一段时间了,它已经集成了 TypeScript 和 CSS 预处理器,在创建项目时勾选上就可以了,不用自己单独去配置,非常的人性化。
异同点#
项目初始架构搭建好之后,接下来就是编写组件了,因为重构的主要目的是熟悉下 TypeScript 的语法,项目的逻辑都是一样的,所以就直接拷贝了以前的代码,只改下其中的语法。除了把一些 js 的工具代码改成了 ts 的后缀之外,再增加了类型检查,主要修改还是.vue文件的script部分的代码,把里面代码的 JavaScript 语法改成 TypeScript 语法。在这里主要有两种写法,一种使用的 Vue 的 extend 方法,用法和以前的输出options类似,还有一种就是采用类和装饰器的写法。
这里采用的是类和装饰器的写法,官方推荐的也是类和装饰器的写法。主要依赖一个第三方库,叫做vue-property-decorator,这个库是在官方的第三方库vue-class-component基础上的扩展的,现在官方也推荐使用这个库。
<!-- Vue.extend -->
<script lang="ts">
import Vue from 'vue'
export default Vue.extend({
props:[],
data() {
return {}
},
computed: {},
methods: {}
// ...
})
</script>
<!-- class + decorator -->
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
// 注意需要使用Component装饰
@Component
export default class YourComponent extends Vue {
@Prop() prop1!: string;
data1: string = "stringExplore";
get computed1(): string {}
method1(arg1: string): void {}
}
</script>因为类的里面只有属性和方法,所以不会和以前一样有很多类别对象,但是在使用上也有一些语法上的差异。由于原项目比较简单,下面简单罗列下项目中重构中遇到的部分差异。
props#
props 为子组件接受父组件传过来的属性对象,这里使用的是第三方库的Prop属性,默认值写在装饰器里面,使用 TypeScript 的语法。
<script lang="ts">
import { Vue, Component, Prop } from "vue-property-decorator";
@Component
export default class YourComponent extends Vue {
@Prop(String) prop1!: string;
@Prop({ default: 0 }) prop2!: number;
@Prop({
default: function () {
return [];
},
})
ratings!: Array<string>;
}
</script>data#
data 是数据或者状态,相当于类的属性,但是需要设置数据的类型。
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
@Component
export default class YourComponent extends Vue {
data1: string = "stringExplore";
data2: Array<string> = ["a", "b", "c"];
}
</script>computed(计算属性)#
计算属性其实也是方法,是特殊的方法,需要要方法前面增加get关键字,表示getter属性。
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
@Component
export default class YourComponent extends Vue {
get computed1(): string {
return "hello";
}
}
</script>methods(方法)#
原来的方法也就是类的方法,直接在类里面编写就行了。
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
@Component
export default class YourComponent extends Vue {
method1(arg1: string): void {
//...
}
method2(arg1: number): void {
//...
}
}
</script>watch(监听器)#
监听器使用第三方库的Watch装饰器。装饰器里面的是监听的对象或者是相关的参数,装饰的方法就是调用的方法。
<script lang="ts">
import { Vue, Component, Watch } from "vue-property-decorator";
@Component
export default class YourComponent extends Vue {
@Watch("child")
onChildChanged(val: string, oldVal: string) {}
@Watch("person", { immediate: true, deep: true })
onPersonChanged(val: Person, oldVal: Person) {}
}
</script>emit 事件派发#
子组件需要给父组件传递信息时,采用事件派发,这里的事件派发使用的是第三方库的装饰器Emit。装饰器里面的是派发的事件(不写的话默认是修饰的方法名),派发的参数就是装饰的方法的参数。
<script lang="ts">
import { Vue, Component, Emit } from "vue-property-decorator";
@Component
export default class YourComponent extends Vue {
count: number = 0;
@Emit("add")
addToCount(n: number) {
this.count += n;
}
}
</script>组件注册#
组件注册需要写在装饰器Component里面。
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
import Comp1 from "../../components/Comp1";
import Comp2 from "../../components/Comp2";
@Component({
components: {
Comp1,
Comp2,
},
})
export default class YourComponent extends Vue {
//...
}
</script>filter(筛选)#
filter 选项也是写在Component装饰器里面的。其实其他的选项(比如 prop,data,methods 等)也是可以写在Component装饰器里面的。
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
@Component({
filter: {
filter1(arg: string): string {},
},
})
export default class YourComponent extends Vue {
//...
}
</script>生命周期#
vue 的生命周期函数相当于类的方法,直接编写即可。
<script lang="ts">
import { Vue, Component } from "vue-property-decorator";
@Component
export default class YourComponent extends Vue {
created() {
// ...
}
mounted() {
// ...
}
}
</script>axios#
原来使用 axios 时,为了在组件中方便使用,是把 axios 赋值到 Vue 的原型中去的,然后通过vue.$axios或者this.$axios在组件中调用。但是使用 TypeScript 不能用这种赋值原型链的方法,可以借助了一个第三方库vue-axios来使用,之后再 vue 组件中就直接通过
(更正:其实也可以使用在原型链赋值的方法,只是需要添加vue.axios或者this.axios来调用了。$axios的类型注解。这和$ref是一个道理)
// main.ts
import Vue from "vue";
import axios from "axios";
// import VueAxios from 'vue-axios'
// Vue.use(VueAxios, axios)
Vue.prototype.$axios = axios;
// Vue Component
import { AxiosInstance } from "axios";
export default class VueComponent extends Vue {
$axios!: AxiosInstance;
}better-scroll#
使用 TypeScript 时需要安装一个声明类文件才可以正常在 Vue.js 组件中正常调用。DefinitelyTyped 库(即@type)已经记录了 90% 的顶级 JavaScript 库。
yarn add @types/better-scroll -D
# npm i @types/better-scroll -D总结#
项目重构之后,基本的功能都能实现,vue 编译也没有错,但是 ts 的编译有一些错误,后面也逐渐修改过来了。
现在对于 TypeScript 还是很粗浅的认识,特别是在类型检查上还不是很了解,不知道怎么更好的使用类型检查,一些参数还是用any来约束的,期待后面继续加深学习,项目也会在后面提交中修改完善类型检查。
在重构中,有个意外之喜,竟然发现了一处拼写的错误,以前竟然没有发现但也没有报错,这让我看到了 TypeScript 类型检查的强大之处。
参考资料#
项目地址#
- 在
vue-elm项目的vue2-ts分支中