基於 Vue3 的組件封裝技巧分享

本文在 Vue3 的基礎上針對一些常見 UI 組件庫組件進行二次封裝,旨在追求更好的個性化,更靈活的拓展,提供一些個人的思路見解,如有不妥之處,敬請指出。核心知識點$attrs,$slots

1、需求說明

需求背景:日常開發中,我們經常會使用一些 UI 組件庫諸如and design vue、element plus等輔助開發,提升效率。有時我們需要進行個性化封裝,以滿足在項目中大量使用的需求。

錯誤示範:基於a-modal封裝一個自定義 Modal 組件:修改modal樣式,按鈕樣式、每次關閉後銷燬、渲染到指定元素上等等,後續項目的彈窗全部基於該自定義組件。

<template>
    <div ref="myModal" class="custom-modal"></div>
    <a-modal
        v-model:visible="visible"
        centered
        destroyOnClose
        :getContainer="() => $refs.myModal"
        @ok="handleOk"
        @cancel="handleCancel"
        :style="{ width: '560px', ...style }"
        :cancelText="cancelText"
        :okText="okText"
    >
        <!-- 以上皆爲該組件的默認屬性 -->
        <slot></slot>
    </a-modal>
</template>

<script setup>
const props = defineProps({
    title: {
        type: String,
        default: "",
    },
    style: {
        type: Object,
        default: () =({}),
    },
    cancelText: {
        type: String,
        default: "取消",
    },
    okText: {
        type: String,
        default: "確定",
    },
});
const emits = defineEmits(["handleOk""handleCancel"]);
const visible = ref(false);

const handleOk = () ={
    emits("handleOk");
};
const handleCancel = () ={
    emits("handleCancel");
};
defineExpose({ visible });
</script>

<style lang="less" scoped>
.custom-modal {
    :deep(.ant-modal) {
        //省略幾百行樣式代碼
    }
}
</style>

代碼封裝完成, 於是乎我們便能在項目中應用帶有項目風格的彈窗

<CustomModal ref="xxxModal" title="xxx" @ok="onXxx" @cancel="onXxx" >content</CustomModal>

2、$attrs

問題來了:一切看起來都挺正常。直到有一天同事說:我想要去掉右上角的關閉按鈕,能改成自定義的嗎

簡單,直接加!

 <!-- 省略不相關代碼 -->
<a-modal :closable="closable"></a-modal>
<script setup>
const props = defineProps({
    //...
    closable:{
        type: Boolean,
        default: false
    }
});
</script>

另一位同事說:我不想讓它是居中的,能改成自定義的嗎,還有一位同事說...

思考:這樣的情況多了,就有點難頂。每次一有新的需求,我就得改這個組件,導致這個組件代碼越來越冗餘。那麼是否有一種方式能夠將傳進來的屬性自動綁定給 a-modal 呢,有,那兒就是attrs

注意:

1.vue 提供了$attrs這麼一個屬性用於接收父組件傳遞下來的屬性,$attrs不包括已經寫入 props 的值

  1. 如果父組件傳遞了 style,class,那麼這這些值不僅會存在於$attrs,還會默認綁定至根元素上。這一點需要注意
<modalTest :footer="null" :centered="false" :zIndex="999" />
//此時的$attrs
{ "footer": null, "centered": false, "zIndex": 999 }

有了這個組件實例,結合v-bind我們就可以這麼寫

    <a-modal
        v-model:visible="visible"
        centered
        destroyOnClose
        :getContainer="() => $refs.myModal"
        :style="{ width: '560px', ...style }"
        v-bind="$attrs"
    >
     <!-- 略  -->
    </a-modal>

這樣一來,我們就可以使用a-modal提供的任意屬性和方法了

3、$slots

問題來了:插槽怎麼辦,例如a-modal就提供了許多插槽,是不是要用哪個就先在自定義組件上寫好呢

錯誤示例:

<a-modal>
    <!-- default -->
    <slot></slot>

    <!-- title -->
    <template #title>
<slot >{{ title }}</slot>
    </template>

    <!-- other -->
</a-modal>

弊端就像之前的,如果該原生提供了許多插槽,當有需要時豈不是頻繁去修改自定義組件添加相應的插槽

其實利用$slots可以解決這個問題

官網的這段話簡明扼要的說出的插槽的原理,我們所傳遞的插槽最終都是變成

{
    'slotName':fn(...args)  //fn返回一個虛擬DOM
    'defautl': fn(...args) //默認插槽
}

也就是我們傳什麼插槽進來,$slots就有什麼值

那麼我們可以遍歷$slots中的值,有什麼插槽我們便動態綁定什麼插槽

<a-modal>
    <template v-for="(_val, name) in $slots" #[name]="options">
        <slot :options || {}"> </slot>
    </template>
</a-modal>

#[name]="options",我們可以拿到原生a-modalname這個插槽中傳遞來的一些狀態options,並綁定在<slot>上。詳情請查看官網:作用域插槽 [1]。

這樣一來,我們原生a-modal怎麼使用插槽,自定義組件就怎麼使用插槽

<CustomModal>
    <template #title="{arg1, arg2}">
        content
    </template>
</CustomModal>

至此,封裝的代碼如下

<template>
    <div ref="myModal" class="custom-modal"></div>
    <a-modal
        v-model:visible="visible"
        centered
        :getContainer="() => $refs.myModal"
        :style="{ width: '560px'}"
        destroyOnClose
        v-bind="$attrs"
    >
        <template v-for="(_val, name) in $slots" #[name]="ops">
            <slot :ops || {}"> </slot>
        </template>
    </a-modal>
</template>

<script setup>
const visible = ref(false);
defineExpose({ visible });
</script>

<style lang="less" scoped>
.custom-modal {
    //style
}
</style>

還有許多優化的空間,例如當前父組件顯隱該Modal需使用ref的方式訪問visible。在 vue3 中也可以參考官網的做法這樣子寫

<template>
    <div ref="myModal" class="custom-modal"></div>
    <a-modal
        :visible="visible"
        //....
        v-bind="$attrs"
    >
          <!-- ...  -->
    </a-modal>
</template>

<script setup>
defineProps(['visible'])
const emit = defineEmits(); // 不用寫"update:visible",vue會自動加上
watch(
    () => props.visible,
    (newVal) => emit("update:visible", newVal);
);
</script>

那麼使用這個控制這個組件的顯示隱藏就方便許多了

<CustomModal v-model:visible="visible"></CustomModal>

本文中的封裝方式是基於 vue3 來進行實現,不侷限在什麼 UI 組件身上。如果使用的是 vue2,情況稍有不同,請自行了解。

作者:MingLin

原文鏈接:https://juejin.cn/post/7278238875457552442

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/zHN36KvgGgEdJTNx9x7ZrA