文章目录
使用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
接受一个参数,是要触发的事件名,如果要出发的事件名和被装饰的方法同名,