文章目录
使用Vue2.6 + TypeScript3.5.3创建了一个项目,搭建过程总结如下。
(2020.01.07)其中有一些最佳实践可能会随着项目的逐渐迭代进行调整,请自行辨别可行性
1 Vue-CLI支持
Vue-CLI内建了TypeScript工具支持,在新建项目时可以选择使用TypeScript扩展,包括了针对Vue Core的官方类型声明,还包括了Vue Router和Vuex提供了相应的声明文件
使用Vue-CLI会自动创建tsconfig.json
文件,基本上使用默认的配置文件就可以满足要求。
2 改造组件
使用TypeScript编写Vue单文件组件有两种方式,一种是通过Vue.extend()
方法,另一种是基于类的Vue组件(在使用Vue-CLI创建项目的时候可以选择),我选择使用了后者,可以提供更优雅、更类似于JSX的书写体验。
需要安装vue-class-component用来将Vue组件改写为基于Class的形式,也可以选择使用vue-property-decorator,后者依赖于前者,而且提供了额外的装饰符,让编写更简单。
使用的时候,将原来导出的类型由对象改为了Class,并且使用@Component
装饰符,如果有要引入的其他子组件,也放到@Component
中。
@Component({
components: {
Child
}
})
export default class HelloVue extends Vue {
// 组件内容
}
要注意,虽然使用了export default
,但是Class的名字还是最好准确定义,这样便于IDE和Lint工具进行追踪、提示。
2.1 组件属性顺序
没有发现Lint和Prettier规则来强制规定组件内的属性顺序,所以约定好一个书写顺序,作为最佳实践
要注意,组件引用、Mixin和Filters都放到了组件外部。总体的顺序分为了三部分:
- 数据( Inject → Prop → Data → Computed → Model → Vuex-State → Vuex-Getter → Proivde )
- 方法(Vuex-Mutation → Vuex-Action → Methods → Watch)
- 钩子函数(生命周期钩子 → 路由钩子)
完整的组件如下,具体写法后面单独列出来(不包含Mixin):
@Component({ components: { Child } })
export default class App extends Vue {
// 数据 (Inject → Prop→ Computed → Model → Vuex-State → Vuex-Getter → Proivde)
// 使用祖先组件注入的数据
@Inject() readonly value1!: string;
// 组件的 Data
value = 'hello';
// 父组件传入 Prop
@Prop(Number) readonly value2!: number;
// 计算属性
get value3(): string {
return this.value1;
}
// 定义 组件的 Model 属性
@Model('change', { type: Boolean, default: false }) checked!: boolean;
// Vuex Store 中定义的 state,作为计算属性定义在组件内
@State value4!: string;
// Vuex Store 中定义的 getter,作为计算属性定义在组件内
@Getter value5!: string;
// 为子孙组件提供数据
@Provide() root = 'Root';
/* ----------------------------------------------- */
// 方法 (Vuex-Mutation → Vuex-Action → Methods → Watch)
// Vuex Store 中定义的 Mutation,作为方法定义在组件内
@Mutation(UPDATE_TITLE_MUTATION) updateTitle!: (payload: { title: string }) => void;
// Vuex Store 中定义的 Action,作为方法定义在组件内
@Action(UPDATE_TITLE_ACTION) updateTitleSync!: () => void;
// 组件内的 Method
get foo(): string {
return this.isCollapse ? 'collapsed-menu' : 'expanded-menu';
}
// 组件内的 Watch
@Watch('value1', { immediate: true, deep: true })
onDataChanged(newVal: string, oldVal: string): void {
this.foo();
}
/* ----------------------------------------------- */
// 钩子函数 (生命周期钩子 → 路由钩子)
beforeCreated()
created()
beforeMount()
mounted() {}
beforeUpdate() {}
updated(){}
activated(){}
deactivated(){}
beforeDetory(){}
destoryed(){}
errorCaptured(){}
beforeRouteEnter(){}
beforeRouteUpdate(){}
beforeRouteLeave(){}
}
2.2 相关API
(1)Data
直接在Class定义即可(实际上就是Class的新语法,与在Class的constructor
中定义相同)
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
msg: number = 123;
}
(2)计算属性
计算属性采取使用getter
的形式定义,在Class内部可以使用get
和set
关键字,设置某个属性的存值函数和取值函数:
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
num: number = 1;
get: value: string() {
return this.num + 1;
}
}
同时定义set
实现了对计算属性的赋值。
(3)@Prop
@Prop
接受的参数就是原来在Vue中props
中传入的参数
import { Vue, Component, Prop } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@Prop(Number) readonly propA: number | undefined
@Prop({ default: 'default value' }) readonly propB!: string
@Prop([String, Boolean]) readonly propC: string | boolean | undefined
}
(4)@PropSync
@PropSync
与Prop
类似,不同之处在于@PropSync
会自动生成一个计算属性,计算属性的getter
返回传入的Prop,计算属性的setter
中会执行Vue中提倡的更新Prop的emit:updatePropName
import { Vue, Component, PropSync } from 'vue-property-decorator'
@Component
export default class YourComponent extends Vue {
@PropSync('name', { type: String }) syncedName!: string
}
相当于:
export default {
props: {
name: {
type: String
}
},
computed: {
syncedName: {
get() {
return this.name
},
set(value) {
this.$emit('update:name', value)
}
}
}
使用时需要配合.sync
修饰符使用(即在组件上定义对应的更新方法):
<hello-sync :my-prop.sync="syncValue" />
<!-- 相当于 -->
<hello-sync :my-prop="syncValue" @update:name="(name) => syncValue = name" />
(5)定义方法
定义方法与Data类型,直接在Class中定义方法即可:
@Component
export default class HelloChild extends Vue {
sayHi(): string {
return 'hello'
}
}
(6)@Watch
使用@Watch
定义侦听器,被装饰的函数就是侦听器执行方法:
@Component
export default class HelloChild extends Vue {
@Watch('msg', { immediate: true, deep: true })
onMsgChanged(newVal: string, oldVal: string): void {
this.oldMsg = oldVal;
}
}
(7)@Emit
想要触发父组件中定义在组件实例上的方法,需要使用@Emit
装饰符。@Emit
接受一个参数,是要触发的事件名,如果要出发的事件名和被装饰的方法同名,那么这个参数可以省略。@Emit
返回值就是传递给事件的参数。
@Component
export default class HelloChild extends Vue {
@Emit()
sayHi(): string {
return 'hello'
}
@Emit('go')
goHere(): string {
return 'gogogo'
}
}
相当于:
export default {
sayHi() {
this.$emit('sayHi', 'hello')
},
goHere() {
this.$emit('go', 'gogogo')
}
}
(8)@Model
一般用来在自定义的组件上使用v-model
,自定义组件中包含可交互元素(例如<input>
或者<checkbox
),当组可交互元素绑定的值发生变化(oninput
、onchange
)时,会传递到父组件绑定的v-model
属性上。
关于自定义组件v-model
的介绍可以参考官方文档。
<template>
<el-checkbox :checked="checked" @change="changeHandler" />
</template>
<script lang="ts">
import { Component, Vue, Model, Emit } from 'vue-property-decorator';
@Component
export default class HelloVModel extends Vue {
@Model('change', { type: Boolean, default: false }) checked!: boolean;
@Emit('change')
changeHandler(checked: boolean) {
return checked;
}
}
</script>
使用的时候:
<hello-v-model v-model="componentVModel" />
自定义组件利用了@Model
,定义了checked
属性,并且利用了@change
事件,当checkbox
发生了change
事件后,父组件中的componentVModel
就会随之发生变化。
实际上Modal
和.sync
修饰符都是Vue为了方便子组件同步数据到父组件而实现的语法糖。
(9)@Ref
当使用ref
属性标记一个子组件或者HTML元素的时候,需要使用@Ref
修饰符来找到标记的组件或元素。例如:
<div ref="someRef"></div>
<hello-ref ref="hello" />
如果我们需要获取ref
引用时:
import { Component, Vue, Watch, Ref } from 'vue-property-decorator';
import HelloRef from '@/views/baseKnowledge/hello-vue/components/HelloRef.vue';
@Component({
components: {
HelloChild,
HelloSync,
HelloVModel,
HelloRef
}
})
export default class HelloVue extends Vue {
@Ref() readonly hello!: HelloRef;
@Ref() readonly someRef!: HTMLDivElement;
}
@Ref
后面跟的参数就是对应的ref
的值,需要为其指定类型,如果是原生的元素,可以使用对应的与内置原生元素类型,如果是自定义组件,那么可以将引入的组件作为类型
如果在HelloRef
中定义了一个notify
方法,我们就可以按照如下调用:
this.hello.notify()
但是现在应该是Vue-Cli内置的Vue类型系统有一个Bug,始终会报如下的错误:
Error:(141, 16) TS2551: Property 'notify' does not exist on type 'Vue'. Did you mean '$notify'?
我的处理方法时,在为hello
定义类型时,手写类型,传入我们需要的方法类型就OK了
@Ref() readonly hello!: { notify: (from?: string) => {} };
(10)Mixins
vue-property-decorator
的Mixins
方法完全来源于vue-class-component
,使用方法如下。首先创建一个Mixin:
// visible-control-mixin.ts
import Vue from 'vue';
import Component from 'vue-class-component';
@Component
export default class MyMixin extends Vue {
visible = false;
get buttonText(): string {
return this.visible ? 'Close' : 'Open';
}
toggleVisible() {
this.visible = !this.visible;
}
}
然后在组件中引入,这时候我们就不再需要组件继承自Vue
了,而是继承自Mixin后的组件,Mixins
方法可以接受个参数,作为混入的Mixin:
import { Component, Mixins } from 'vue-property-decorator';
import VisibleControlMixin from '@/mixins/visible-control-mixin';
@Component
export default class MixinExample extends Mixins(VisibleControlMixin) {}
(11)@Inject
/@Provide
provide
和inject
主要的目的就是透传属性,从一个