二次封装组件

2023-10-11 09:58:24发布
69

在工作中,有时候我们需要对第三方的组件进行二次封装(比如element-ui),通常我们会遇到以下三个问题 :

1. 属性与事件传值问题

2. 插槽问题

3. ref问题

下面以二次封装element-ui组件为例来解决上面3个问题。


属性与事件传值问题

比如我现在对el-input进行二次封装,组件的名字叫MyInput.vue

<template>
    <div>
        <el-input></el-input>
    </div>
</template>

在App.vue上使用

<template>
  <MyInput v-model="val" @blur="blur" placeholder="请输入" clearable></MyInput>
</template>

<script setup>
import { ref } from 'vue'
import MyInput from './components/MyInput.vue'

const val = ref('');

const blur = ()=> {
  console.log(val.value);
}
</script>

很明显,这里的属性和事件

<MyInput v-model="val" @blur="blur" placeholder="请输入" clearable></MyInput>

无法传递到MyInput.vue的el-input

解决方法是给MyInput.vue的el-input绑定$attrs属性,如下 :

<template>
    <div>
        <el-input v-bind="$attrs"></el-input>
    </div>
</template>

$attrs可以接收不是props属性的值,比如我给MyInput绑定一个name属性:

<MyInput v-model="val" @blur="blur" placeholder="请输入" clearable :name="'MyInput'"></MyInput>

然后我在MyInput用props接收

<template>
    <div>
        <el-input v-bind="$attrs"></el-input>
    </div>
</template>
<script setup>
import { onMounted, defineProps, getCurrentInstance } from 'vue'
defineProps({
    name: {
        type: String,
        default: "",
    },
});

onMounted(() => {
    const app = getCurrentInstance();
    console.log(app.attrs)
})
</script>

打印结果

$attrs属性里面并没有name属性,因为name属性是props属性


插槽问题

接下来是插槽的问题,element ui的el-input给我们提供了4个插槽,如下 :

现在需要把这些插槽写到MyInput.vue组件去。如下 :

<template>
    <div>
        <el-input v-bind="$attrs">
            <template #prefix>
                <slot name="prefix"></slot>
            </template>
            <template #suffix>
                <slot name="suffix"></slot>
            </template>
            <template #prepend>
                <slot name="prepend"></slot>
            </template>
            <template #append>
                <slot name="append"></slot>
            </template>
        </el-input>
    </div>
</template>

使用插槽

<template>
  <MyInput v-model="val" @blur="blur" placeholder="请输入" clearable :name="'MyInput'">
    <template #prefix>
      <div>prefix</div>
    </template>
    <template #suffix>
      <div>suffix</div>
    </template>
    <template #prepend>
      <div>prepend</div>
    </template>
    <template #append>
      <div>append</div>
    </template>
  </MyInput>
</template>

运行效果

这么写貌似还可以,但是有一个点不是很好,就是我不一定所有的插槽都会用到。我可能只会用其中的一个,但是MyInput.vue里面却把4个插槽都写了,代码有点冗余。

解决的方法是使用$slots属性,$slots属性可以获取到组件的插槽数据(注意,vue的插槽数据实际上是一个Object对象),我们可以打印出来看看

<script setup>
    import { getCurrentInstance } from 'vue'

    console.log(getCurrentInstance().slots);
</script>

打印结果

能拿到这些数据,我们就可以动态生成slot,如下 :

MyInput.vue

<template>
    <div>
        <el-input v-bind="$attrs">
            <template v-for="(slot, key) in $slots" #[key]>
                <slot :name="key"></slot>
            </template>
        </el-input>
    </div>
</template>