vue-cli3.x项目中使用TypeScript

简介

使用vue-cli3.x脚手架搭建基于TypeScript的项目。

使用对象的写法与以前类似,这里不再累述

本文介绍的是使用类(class)的写法

vue-cli2.x升级成vue-cli3.x参考:vue-cli2升级成vue-cli3
Typescript基础知识参考:TypeScript笔记
修饰器相关知识参考:修饰器Decorator笔记

搭建

vue create project-name # 或者图形化搭建 vue ui

搭建的时候选择typescript

扩展库

vue-class-component

  • 官方出品
  • 提供了VueComponent
  • 用类的方式编写组件。这种编写方式可以让.vue文件的js域结构更扁平,并使vue组件可以使用继承、混入等高级特性
  • 开发一般不用vue-class-component,直接使用vue-property-decorator就行
import Vue from 'vue' import Component from 'vue-class-component' @Component({ props: { child: String }, watch:{ }, components: { } }) export default class ComponentA extends Vue { // initial data nameString = `ComponentA` // computed get computedMsg () { return 'computed ' + this.nameString } // lifecycle hook mounted () { this.greet() } // method greet () { console.log('greeting: ' + this.nameString) } }

vue-property-decorator

  • vue-class-component的基础上扩展出来的
  • 社区出品
  • 深度依赖了vue-class-component,拓展出了更多操作符:@Prop@Emit@Inject@Model@Provide@Watch
  • VueComponent也可以通过vue-property-decorator引入
  • 开发时正常引入vue-property-decorator就行
import { Component, Prop, Vue } from 'vue-property-decorator' @Component export default class App extends Vue { name: string = 'Simon Zhang' // computed get MyName(): string { return `My name is ${this.name}` } // methods sayHello(): void { alert(`Hello ${this.name}`) } mounted() { this.sayHello() } }

vuex-class

参考:基于Typescript的Vue项目中使用vuex

常见写法

组件声明

import { Component, Vue } from 'vue-property-decorator' @Component export default class Test extends Vue { // ... }
import { Component, Vue } from 'vue-property-decorator' // Component组件装饰器,用于增加一些全局的配置,比如过滤器filters @Component({ components: { // components 里面放一些全局的组件,引入子组件后写在这,跟vue中的components一样 } }) export default class Test extends Vue { // ... }

data对象

import { Component, Prop, Vue } from 'vue-property-decorator' @Component export default class Test extends Vue { private str: string = 'hello' private name: string constructor() { super() this.name = '喵巨人' } }

计算属性

直接通过get加函数名的形式

import { Component, Vue } from 'vue-property-decorator' @Component export default class Test extends Vue { private radius: number = 3 private get area() { return Math.PI * this.radius * this.radius } }

生命周期函数

import { Component, Vue } from 'vue-property-decorator' @Component export default class Test extends Vue { public created(): void { console.log('created') } }

自定义方法

import { Component, Vue } from 'vue-property-decorator' @Component export default class Test extends Vue { private handleClick(): void { console.log(this.name) } }

监听属性

import { Component, Vue, Watch } from 'vue-property-decorator' @Component export default class Test extends Vue { @Watch('name', { immediate: true, deep: true }) public onChildChanged(val: string, oldVal: string) { console.log('watch new name=' + val) } }

Prop声明

import { Component, Prop, Vue } from 'vue-property-decorator' @Component export default class Test extends Vue { @Prop() private msg: string @Prop({ default: 'default' }) private propB: string @Prop([String, Boolean]) private PropC: string | boolean @Prop(Object) private list!: any; //!表示这个值必须传,不然报错,强制性。。 // 感叹号是非 null 和非 undefined 的类型断言,所以上面的写法就是对 list 这个属性进行非空断言 @Prop({ type: Boolean, // 父组件传递给子组件的数据类型 required: true, // 是否必填 default: false // 默认值,如果传入的是Object,则要 default: () => ({}) 参数为函数 }) collapsed: boolean }

Emit事件

import { Component, Vue, Emit } from 'vue-property-decorator' @Component export default class Test extends Vue { private count = 0 @Emit() private addToCount(n: number) { this.count += n } @Emit('reset') private resetCount() { this.count = 0 } @Emit() private returnValue() { return 10 } }

相当于

export default { data() { return { count: 0 } }, methods: { addToCount(n) { this.count += n this.$emit('add-to-count', n) }, resetCount() { this.count = 0 this.$emit('reset') } returnValue() { this.$emit('return-value', 10) } } }

父子组件传值

方法一:不使用 @Emit()

  • 父组件通过自定义属性将属性值传给子组件

  • 子组件引入Prop修饰器,通过@Prop()接收

  • 子组件通过$emit通知父组件修改

  • 父组件绑定事件处理函数

父组件的html代码

<sub-com :count="count" @add="handleAdd"></sub-com>

父组件的typescript代码

import { Component, Vue, Emit } from 'vue-property-decorator' import subCom from 'components/subcom.vue' @Component({ components: { subCom } }) export default class Test extends Vue { private count: number = 0 private handleAdd(offset: number) { this.count += offset } }

子组件的typescript代码

import { Component, Vue, Prop } from 'vue-property-decorator' @Component export default class Subcom extends Vue { @Prop() private count!: number private countAdd() { this.$emit('add', 1) } }

方法二:使用 @Emit()

  • 父组件通过自定义属性将属性值传给子组件

  • 子组件引入Prop修饰器,通过@Prop()接收

  • 子组件引入Emit修饰器,通过向父组件传递事件

  • 父组件绑定事件处理函数

父组件的html代码

<sub-com :count="count" @add="handleAdd"></sub-com>

父组件的typescript代码

import { Component, Vue, Emit } from 'vue-property-decorator' import subCom from 'components/subcom.vue' @Component({ components: { subCom } }) export default class Test extends Vue { private count: number = 0 private handleAdd(offset: number) { this.count += offset } }

子组件的typescript代码

import { Component, Vue, Prop, Emit } from 'vue-property-decorator' @Component export default class Subcom extends Vue { @Prop() private count!: number @Emit('add') private countAdd() { return 1 } }

过滤器

  • src/目录下新建filters/目录
  • src/filters/目录下新建index.ts文件
export function formatTime(value: number): string { if (!value) { return '' } const date: Date = new Date() date.setTime(Number(value) * 1000) let year: number = date.getFullYear() let month: number | string = date.getMonth() + 1 let day: number | string = date.getDate() let hour: number | string = date.getHours() let minute: number | string = date.getMinutes() let second: number | string = date.getSeconds() month = ('00' + month).slice(-2) day = ('00' + day).slice(-2) hour = ('00' + hour).slice(-2) minute = ('00' + minute).slice(-2) second = ('00' + second).slice(-2) return `${year}-${month}-${day} ${hour}:${minute}:${second}` }
  • 在组件局部使用:通过filters注入局部过滤器函数
<div>{{ time | formatTime }}</div>
import { Component, Vue } from 'vue-property-decorator' import { formatTime } from '@/filters' @Component({ filters: { formatTime } }) export default class Test extends Vue { private time: number = 1573712728 }
  • 全局注册:修改src/main.ts文件
import * as filters from './filters' // 全局filter // register global utility filters. Object.keys(filters).forEach(key => { Vue.filter(key, (filters as any)[key]) })

指令

  • src/目录下新建directives/目录
  • src/directives/目录下新建index.ts文件
export const focus = { // 当被绑定的元素插入到DOM中时 inserted: function (el: HTMLElement) { // 聚焦元素 el.focus() } }
  • 在组件局部使用
<input v-focus v-model="modelData">
import { Component, Vue } from 'vue-property-decorator' import { focus } from '@/directives' @Component({ directives: { focus } }) export default class Test extends Vue { private modelData: string }

混入

混合公用的属性或函数在template中使用时可以定义为private

如果在生命周期中使用需要定义为public,然后还需在shims-vue.d.ts文件中将混合公用的属性或函数声明下(或者直接告诉它这个肯定有(this as any).consoleStr())。

  • src/目录下新建mixins/目录
  • src/mixins/目录下新建test.ts文件
// 定义混合属性或函数 import { Vue, Component } from 'vue-property-decorator' @Component export default class MixinTest extends Vue { private message: string = 'hello word' public consoleStr() { this.$notify('测试混合函数'); } }
  • 在组件文件中使用
<div>{{ message }}</div> <van-button type="primary" @click="consoleStr">Notify</van-button>
import { Component, Vue } from 'vue-property-decorator' import MixinTest from '../mixins/test' @Component({ mixins: [MixinTest] }) export default class Test extends Vue { public created() { (this as any).consoleStr() } }

常见问题

获取dom

// 加上 as HTMLDivElement let docEl = document.documentElement as HTMLDivElement

获取refs

在其后面加上as HTMLDivElement

let layoutList: any = this.$refs.layout as HTMLDivElement // 或着 let fieldCalculate: any = (this as any).$refs.fieldCalculate

Property ‘deviceId’ has no initializer and is not definitely assigned in the constructor

属性deviceId还没有初始化方法(initializer)而且在构造器(constructor)中也没有明确指出

Typescript 2.7 release版本出来后,设置了一个新的编译器选项strictPropertyInitialization
当本选项strictPropertyInitialization:true的时候,编译器会确保一个类中所有的属性都已经初始化,如果没有,那么在属性构建的过程中就会抛出错误

解决办法:
修改tsconfig.json文件,将其中的strictPropertyInitialization值修改成false

{ "compilerOptions": { // ... "strictPropertyInitialization": false // ... }, // ... }

相关链接


创作不易,若本文对你有帮助,欢迎打赏支持作者!

 分享给好友: