优雅的封装一个高阶组件

一、前置场景

前端小伙伴在开发过程中,避免不了使用第三方 UI 组件库快速进行迭代开发,比如在 PC 端 vue 项目中,使用Element UI,对于大部分童鞋来说那是手到擒来的事情吧?但是由于业务需求千奇百怪,通过第三方组件库的拿来主义偶然会捉襟见肘的,况且这个需求是一个复用性比较高的,那么此时我们就需要考虑进行组件封装,才能达到一劳永逸的效果!

二、案例举证

业务需求:有一个导入物流信息的功能按钮,而这个功能又区分维度,比如按发货单导入、按FBA货件导入、按物流信息编码导入等。如果把这个三个功能分别用 3 个按钮来实现,完全没有问题,但是产品提出了一个更好的建议,把相关功能按钮通过一个按钮选项来实现,让用户去选择操作。

三、组件封装实现方案

1.第一种方案

a. 封装的组件MoreSelect.vue如下(vue + js 版本):

<template>
  <el-select @change="handleChange" :placeholder="placeholder" value="" :size="size">
    <el-option v-for="item in list" :key="item.value" :value="item">
      {{ item.name }}
    </el-option>
  </el-select>
</template>
<script>
export default {
  name: 'MoreSelect',
  props: {
    size: {
      type: String,
      default: 'medium',
    },
    placeholder: {
      type: String,
      default: '请选择',
    },
    list: {
      type: Array,
      default: [],
    },
  },
  methods: {
    handleChange(e) {
      this.$emit('handleChange', e)
    },
  },
}
</script>

b. 封装的组件MoreSelect.vue如下(vue + ts 版本):

<template>
  <el-select @change="handleChange" :placeholder="placeholder" value="" :size="size">
    <el-option v-for="item in list" :key="item.value" :value="item">
      {{ item.name }}
    </el-option>
  </el-select>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'

interface IOption {
  name: string
  value: string
}

@Component({
  name: 'MoreSelect',
})
export default class MoreSelect extends Vue {
  @Prop({ type: String, default: 'medium' }) size!: 'medium' | 'small' | 'mini'
  @Prop({ type: String, default: '请选择' }) placeholder!: string
  @Prop({ type: Array, default: [] }) list!: Array<IOption>

  handleChange(e: IOption) {
    this.$emit('handleChange', e)
  }
}
</script>

在 template 模版里面使用如下:

  <MoreSelect
    size="small"
    :list="[
      { name: '按发货单导入', value: '1' },
      { name: '按FBA货件导入', value: '2' },
      { name: '按物流信息编码导入', value: '3' },
    ]"
    placeholder="导入物流信息"
    @handleChange="handleChange"
  ></MoreSelect>

el-select 上面绑定value="",避免开发环境报错

到此一个组件已经完美封装完成啦!!!!!!!


2.第二种方案

基于第一种方案,我们在封装的组件中重新定义了一系列的属性 props(size、placeholder、list)和事件 event(handleChange),然后再传给 elementUI 的组件 el-select,那么我们能否不定义属性和事件直接使用 el-select 的属性和事件呢 ?答案是可以的
vue2.4.0版本新增的 2 个属性$attrs$listeners能够完美解决此问题,也是优雅封装高阶组件的灵魂。 废话不说直接上代码如下:

a. 封装的组件MoreSelect.vue如下(vue + js 版本):

<template>
  <el-select v-bind="$attrs" v-on="$listeners">
    <el-option v-for="item in list" :key="item.value" :value="item">
      {{ item.name }}
    </el-option>
  </el-select>
</template>

<script>
export default {
  name: 'MoreSelect',
  // false:禁止属性绑定到跟元素上,默认为 true
  inheritAttrs: false,
  props: {
    list: {
      type: Array,
      default: [],
    },
  },
}
</script>

b. 封装的组件MoreSelect.vue如下(vue + ts 版本):

<template>
  <el-select v-bind="$attrs" v-on="$listeners">
    <el-option v-for="item in list" :key="item.value" :value="item">
      {{ item.name }}
    </el-option>
  </el-select>
</template>

<script lang="ts">
import { Component, Prop, Vue } from 'vue-property-decorator'

interface IOption {
  name: string
  value: string
}

@Component({
  name: 'MoreSelect',
  inheritAttrs: false,
})
export default class MoreSelect extends Vue {
  @Prop({ type: Array, default: [] }) list!: Array<IOption>
}
</script>

在 template 模版里面使用如下:

// 注意此时的组件拥有el-select的所有属性和事件,无需再组件里面声明
  <MoreSelect
    size="small"
    :list="[
      { name: '按发货单导入', value: '1' },
      { name: '按FBA货件导入', value: '2' },
      { name: '按物流信息编码导入', value: '3' },
    ]"
    value=""
    placeholder="导入物流信息"
    @change="handleChange"
  ></MoreSelect>

四、vue2 是不是过时啦?俺也不太清楚,那么在 vue3 中如何使用呢?

vue3代码如下:

<template>
  <el-select v-bind="attrs">
    <el-option v-for="item in list" :value="item">{{ item.name }}</el-option>
  </el-select>
</template>

<script lang="ts" setup>
import { useAttrs } from 'vue';

type IOption = {
  name: string;
  value: string;
};

const attrs = useAttrs();
defineProps({
  list: {
    type: Array<IOption>,
    default: [],
  },
  value: String,
});
</script>
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容