基于TypeScript搭建Vue项目初试

国庆前夕,Vue.js 作者发布了关于 Vue.js v3.0 开发计划的文章,文章中有提到将会使用 TypeScript 重新编写 Vue.js 的源码,使得源码结构更清晰,更易维护。因此觉得是时候学习下 TypeScript 了。

目录
  1. 构建
  2. 异同点
    1. props
    2. data
    3. computed(计算属性)
    4. methods(方法)
    5. watch(监听器)
    6. emit 事件派发
    7. 组件注册
    8. filter(筛选)
    9. 生命周期
    10. axios
    11. better-scroll
  3. 总结
  4. 参考资料
  5. 项目地址

国庆前夕,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分支中