概述
在需求开发的过程中,不可避免地会遇到重复功能模块或相似功能模块的开发,在原有功能的基础上开发新功能有两种方式:
- copy-paste,直接把原有的代码复制一遍到新功能模块,然后再对原有代码中不符合当前场景的交互,逻辑,样式进行自定义修改;
- 另外一种是先将原有功能模块重构,先将不可复用的代码抽出可复用的代码,然后将原有功能模块作为可复用代码的首次实践,在不改变功能的前提下用复用已有代码的形式重写代码,针对新功能模块,首先在可复用代码上暴露出可扩展点,新功能在复用已有代码的基础上可以自己对扩展点进行扩展,从而实现与原有模块不同的功能。
特别的,针对以.vue文件实现的功能,总结出以下从.vue文件抽离可复用组件的一般性方法
.vue文件结构特点
Vue 的单文件组件是网页开发中 HTML、CSS 和 JavaScript 三种语言经典组合的自然延伸。template、script 和 style 三个块在同一个文件中封装、组合了组件的视图、逻辑和样式。
重构策略
针对template和style块,可以通过在原有组件中引用组件的形式抽离并使用公共代码;
针对script块,可以通过Vue混入的方式抽离并使用公共代码
- 文件拆分
将.vue文件中的template>和style块拆分出来,形成新的.template.vue文件
将.vue文件中的script块拆分出来,形成新的.mixin.ts文件
- 拆分文件扩展性适配
a .template.vue
script块除了继承Vue声明外,内部只有一个Prop属性vm,指向原组件的this
template块中可以对识别出来的扩展点用slot元素包裹,方便后续定制化开发,可以用计算属性代替一些文本类展示,方便后续二开
style中拥有原来的所有样式属性
b .mixin.ts文件
生命周期钩子函数:针对mounted和created类似的钩子函数,鉴于混入时不是覆盖模式,而是追加模式,只抽离明确可复用的部分
其余属性:将可复用属性保留,其余明确的业务属性放回至原来的.vue文件
方法:方法中的逻辑只保留可复用逻辑,针对方法中的某一小段业务逻辑,可以单独抽出方法,后续可以通过混入的函数覆盖来实现定制化
- 重写拆分前的.vue文件,引用拆分出的可扩展组件,通过子组件和混入的方式改造组件
- 针对另外一个模块相似功能的.vue文件,同样引用拆分出的可扩展组件,通过子组件和混入的方式改造组件,改造过程中如果识别出新的扩展点可以对可扩展组件进行调整
重构示例
重构前,A组件和B组件都是导入功能的不同业务实现
<template>
<div class="wrapper">
<div class="block1">
<Component1>
<Component2>
<div>
<div class="block2">
<span>这里渲染的是组件3</span>
<Component3 v-model="businessAttr1" @emitInfo="buisnessFunc1">
</div>
</div>
</template>
<script lang="ts">
import { Vue, Prop, Watch, Component } from 'vue-property-decorator';
@Component({
name: 'Component',
components: {
},
})
export default class Component extends Vue {
@Prop()
list!: any[];
businessAttr1: any = {};
buisnessFunc1() {
console.log('buisnessFunc1');
}
}
</script>
<style lang="less" scoped>
.wrapper {
width: 80%;
.block1 {
height: 200px;
}
.block2 {
height: 100px;
}
}
</style>
<template>
<div class="wrapper">
<div class="block1">
<Component1>
<Component2>
<div>
<div class="block2">
<span>这里渲染的是组件4</span>
<Component4 v-model="businessAttr2" @emitInfo="buisnessFunc2">
</div>
</div>
</template>
<script lang="ts">
import { Vue, Prop, Watch, Component } from 'vue-property-decorator';
@Component({
name: 'Component',
components: {
},
})
export default class Component extends Vue {
@Prop()
list!: any[];
businessAttr2: any = {};
buisnessFunc2() {
console.log('buisnessFunc2');
}
}
</script>
<style lang="less" scoped>
.wrapper {
width: 80%;
.block1 {
height: 200px;
}
.block2 {
height: 100px;
}
}
</style>
重构后
<template>
<IndexTemplate :importVM="_self">
<template slot="customArea">
<Component3 v-model="businessAttr1" @emitInfo="buisnessFunc1">
</template>
</IndexTemplate>
</template>
<script lang="ts">
import { Component, Prop, Watch } from 'vue-property-decorator';
import { mixins } from 'vue-class-component';
import ImportMixin from 'import.mixin';
import ImportTemplate from 'import.template.vue';
@Component({
name: 'DataImport',
components: {
},
})
export default class Component extends mixins(ImportMixin) {
get info() {
return '这里渲染的是组件3';
}
}
</script>
<style lang="less" scoped></style>
<template>
<IndexTemplate :importVM="_self">
<template slot="customArea">
<Component3 v-model="businessAttr1" @emitInfo="buisnessFunc1">
</template>
</IndexTemplate>
</template>
<script lang="ts">
import { Component, Prop, Watch } from 'vue-property-decorator';
import { mixins } from 'vue-class-component';
import ImportMixin from 'import.mixin';
import ImportTemplate from 'import.template.vue';
@Component({
name: 'DataImport',
components: {
},
})
export default class Component extends mixins(ImportMixin) {
get info() {
return '这里渲染的是组件4';
}
}
</script>
<style lang="less" scoped></style>
<template>
<div class="wrapper">
<div class="block1">
<Component1>
<Component2>
<div>
<div class="block2">
<span>{{vm.info}}</span>
<slot name="customArea"></slot>
</div>
</div>
</template>
<script lang="ts">
import { Vue, Prop, Watch, Component } from 'vue-property-decorator';
@Component({
name: 'Component',
components: {
},
})
export default class Component extends Vue {
@Prop() vm!: any;
}
</script>
<style lang="less" scoped>
.wrapper {
width: 80%;
.block1 {
height: 200px;
}
.block2 {
height: 100px;
}
}
</style>
import { Vue, Prop, Watch, Component } from 'vue-property-decorator';
@Component
export default class Component extends Vue {
@Prop()
list!: any[];
get info() {
return '这里渲染的是组件?'
}
}