可复用Vue组件重构指南

207 阅读4分钟

概述

在需求开发的过程中,不可避免地会遇到重复功能模块或相似功能模块的开发,在原有功能的基础上开发新功能有两种方式:

  1. copy-paste,直接把原有的代码复制一遍到新功能模块,然后再对原有代码中不符合当前场景的交互,逻辑,样式进行自定义修改;
  2. 另外一种是先将原有功能模块重构,先将不可复用的代码抽出可复用的代码,然后将原有功能模块作为可复用代码的首次实践,在不改变功能的前提下用复用已有代码的形式重写代码,针对新功能模块,首先在可复用代码上暴露出可扩展点,新功能在复用已有代码的基础上可以自己对扩展点进行扩展,从而实现与原有模块不同的功能。

特别的,针对以.vue文件实现的功能,总结出以下从.vue文件抽离可复用组件的一般性方法

.vue文件结构特点

Vue 的单文件组件是网页开发中 HTML、CSS 和 JavaScript 三种语言经典组合的自然延伸。template、script 和 style 三个块在同一个文件中封装、组合了组件的视图、逻辑和样式。

重构策略

针对template和style块,可以通过在原有组件中引用组件的形式抽离并使用公共代码;

针对script块,可以通过Vue混入的方式抽离并使用公共代码

  1. 文件拆分

将.vue文件中的template>和style块拆分出来,形成新的.template.vue文件

将.vue文件中的script块拆分出来,形成新的.mixin.ts文件

  1. 拆分文件扩展性适配

a .template.vue

script块除了继承Vue声明外,内部只有一个Prop属性vm,指向原组件的this

template块中可以对识别出来的扩展点用slot元素包裹,方便后续定制化开发,可以用计算属性代替一些文本类展示,方便后续二开

style中拥有原来的所有样式属性

b .mixin.ts文件

生命周期钩子函数:针对mounted和created类似的钩子函数,鉴于混入时不是覆盖模式,而是追加模式,只抽离明确可复用的部分

其余属性:将可复用属性保留,其余明确的业务属性放回至原来的.vue文件

方法:方法中的逻辑只保留可复用逻辑,针对方法中的某一小段业务逻辑,可以单独抽出方法,后续可以通过混入的函数覆盖来实现定制化

  1. 重写拆分前的.vue文件,引用拆分出的可扩展组件,通过子组件和混入的方式改造组件
  2. 针对另外一个模块相似功能的.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 '这里渲染的是组件?'
  }
}