Slots Emit和Props穿透組件封裝實(shí)現(xiàn)摸魚加鐘
??背景
組內(nèi)多人共同開(kāi)發(fā)時(shí)免不了基于某UI庫(kù)二次封裝組件來(lái)適應(yīng)項(xiàng)目業(yè)務(wù)場(chǎng)景的情況,但不知道大家有沒(méi)有遇到過(guò)需要兼容部分或者穿透子組件全部Props或者Slots的情況,這種時(shí)候如果針對(duì)每一個(gè)Props或者Slots去單獨(dú)處理穿透不僅費(fèi)時(shí)費(fèi)力而且代碼會(huì)越來(lái)越臃腫難以維護(hù),所以想在這里通過(guò)一個(gè)簡(jiǎn)單的例子來(lái)對(duì)比一下Slots、Props、Emit的各種穿透方案
????準(zhǔn)備工作
首先新建我們需要用到的子組件,如下
Card.vue
<template>
<div class="card-container">
<div @click="handleClose" class="card-close">
<!-- 先用X來(lái)代替 -->
<span>X</span>
</div>
<div class="card-title">
<slot name="title">
<!-- 默認(rèn)使用props作為title,有slot則優(yōu)先slot -->
{{props.title}}
</slot>
</div>
<div class="card-content">
<slot>
<!-- content這里也是,一切都以slot優(yōu)先 -->
{{props.content}}
</slot>
</div>
<div class="card-footer">
<slot name="footer">
<!-- footer這里也是,一切都以slot優(yōu)先 -->
{{props.footer}}
</slot>
</div>
</div>
</template>
<script lang="ts" setup>
import { defineProps, defineEmits } from 'vue'
interface ChildrenProps {
title?: String
handleClose?: Function
}
const props = defineProps<ChildrenProps>()
const emits = defineEmits(['close'])
// 響應(yīng)點(diǎn)擊事件
const handleClose = () => {
// 這邊演示方便,直接調(diào)props之后跟上emit調(diào)用
props.handleClose && props.handleClose()
emits('close')
}
</script>
...css部分略過(guò)
再來(lái)準(zhǔn)備一個(gè)Button.vue
<template>
<button @click="handleClick">
<slot name="prefix"></slot>
<slot>
{{props.title}}
</slot>
<slot name="suffix"></slot>
</button>
</template>
<script lang="ts" setup>
import { withDefaults, defineProps, defineEmits } from 'vue'
interface ButtonProps {
title?: string,
handleClick?: Function
}
const emits = defineEmits(['click'])
const props = withDefaults(defineProps<ButtonProps>(), {
title: 'DONE'
})
const handleClick = () => {
emits('click')
props.handleClick && props.handleClick()
}
</script>
以及我們需要實(shí)現(xiàn)的ProCard.vue
Slots穿透方案-單子組件
使用Vue提供的Dynamic directive arguments結(jié)合v-slot指令 Dynamic directive arguments部分文檔鏈接 在單子組件的情況下穿透Slot比較簡(jiǎn)單,不需要考慮太多的Slot覆蓋問(wèn)題,只需要關(guān)注封裝組件自身Slot命名即可,如有命名重復(fù)情況可參考多子組件方案解決,比如下面這個(gè)ProCard.vue,只用到了Card組件
<template>
<div class="procard-container">
<PureCard>
<template
v-for="(slotKey, slotIndex) in slots"
:key="slotIndex" v-slot:[slotKey]
>
<slot :name="slotKey"></slot>
</template>
</PureCard>
</div>
</template>
<script lang="ts" setup>
import { useSlots } from 'vue'
import PureCard from '../Card/Card.vue'
const slots = Object.keys(useSlots())
</script>
使用
<template>
<div>
<ProCard>
<template #title>
<span>CardSlot標(biāo)題</span>
</template>
</ProCard>
</div>
</template>
效果

Slots穿透方案-多子組件
通常我們封裝業(yè)務(wù)組件時(shí)一般不至于一個(gè)子組件,但多個(gè)子組件的情況下就要特別注意Slot命名情況了,這邊分享一個(gè)在平時(shí)開(kāi)發(fā)時(shí)我們選擇的一個(gè)方案:使用不同前綴來(lái)區(qū)分不同slot,props也是同理。在ProCard.vue中我們加入一個(gè)Button組件,協(xié)商約定c-xxx為Card組件Slot,b-xxx為Button組件Slot,這樣在經(jīng)過(guò)分解之后就可以區(qū)分出應(yīng)該往哪個(gè)組件穿透Slot了。
在ProCard.vue中取的所有slots并且理好各個(gè)組件所需slots
// 首先還是取到所有Slots的key
const slots = Object.keys(useSlots())
// 定義一個(gè)buttonSlots,用來(lái)組裝需要用到的Button組件的slots
const buttonSlots = ref<string[]>([])
// 定義一個(gè)cardSlots,用來(lái)組裝需要用到的Card組件的slots
const cardSlots = ref<string[]>([])
// 找出各自組件需要的slot組裝好push進(jìn)去就可以在template中穿透到指定組件了
for (let slotIndex = 0; slotIndex < slots.length; slotIndex++) {
const slotKey = slots[slotIndex];
if (slotKey.indexOf('c-') > -1) {
cardSlots.value.push(slotKey.slice(2, slotKey.length))
continue
}
if (slotKey.indexOf('b-') > -1) {
buttonSlots.value.push(slotKey.slice(2, slotKey.length))
}
}
接下來(lái)就可以在template中直接使用了
<template>
<div class="procard-container">
<PureCard
@close="onEmitClose"
:handleClose="handleClose"
>
<!-- 使用組裝好的cardSlots -->
<template
v-for="(slotKey, slotIndex) in cardSlots"
:key="slotIndex"
v-slot:[slotKey]
>
<slot :name="`c-${slotKey}`">{{slotKey}}</slot>
</template>
</PureCard>
<PureButton
@click="onButtonClick"
:handleClick="handleButtonClick"
>
<!-- 使用組裝好的buttonSlots -->
<template
v-for="(slotKey, slotIndex) in buttonSlots"
:key="slotIndex"
v-slot:[slotKey]
>
<slot :name="`b-${slotKey}`">{{slotKey}}</slot>
</template>
</PureButton>
</div>
</template>
引入一下ProCard組件來(lái)看一下效果吧
<template>
<div>
<ProCard title="123">
<template #c-title>
<span>CardSlot標(biāo)題</span>
</template>
<template #c-default>
<span>CardSlot內(nèi)容</span>
</template>
<template #c-footer>
<span>CardSlot底部</span>
</template>
<template #b-default>
按鈕
</template>
</ProCard>
</div>
</template>

成功實(shí)現(xiàn)了多組件Slots穿透
Props和Emit穿透方案-單子組件
Props和Emit的穿透方式與Slots的方案類似,使用v-bind直接綁定組件Attributes是最方便的穿透方式,但缺點(diǎn)也很明細(xì),直接v-bind所有Attributes可能會(huì)導(dǎo)致命名重復(fù)所帶來(lái)的各種連鎖問(wèn)題,如果像上文slots一樣通過(guò)前綴來(lái)區(qū)分組裝又有點(diǎn)繁瑣,所以如果是多子組件的情況下推薦使用下面的props+v-bind方案。
單子組件這邊在ProCard中使用useAttrs來(lái)得到子組件上所有的attributes,再使用v-bind直接轉(zhuǎn)發(fā)到ProCard的子組件,這樣就可以直接穿透Props和Emit了非常方便好用
// 獲取到組件所有的attributes const attrs = useAttrs()
template中轉(zhuǎn)發(fā)到子組件
<PureCard
@close="onEmitClose"
:handleClose="handleClose"
v-bind="attrs"
>
<!-- 使用組裝好的cardSlots -->
<template
v-for="(slotKey, slotIndex) in cardSlots"
:key="slotIndex"
v-slot:[slotKey]
>
<slot :name="`c-${slotKey}`"></slot>
</template>
</PureCard>
父組件調(diào)用ProCard
<script setup lang="ts">
import ProCard from './components/ProCard/ProCard.vue'
const handleClose = () => {
console.log('parent handleClose')
}
const onClose = () => {
console.log('parent onClose')
}
</script>
<template>
<ProCard
title="123"
@close="onClose"
:handleClose="handleClose"
>
<template #c-title>
<span>CardSlot標(biāo)題</span>
</template>
<template #c-default>
<span>CardSlot內(nèi)容</span>
</template>
<template #c-footer>
<span>CardSlot底部</span>
</template>
<template #b-default>
按鈕
</template>
</ProCard>
</template>
看一下實(shí)際效果

點(diǎn)擊一下右上角關(guān)閉按鈕

可以看到成功穿透了Emit和Props并且被子組件給執(zhí)行了
Props和Emit穿透方案-多子組件
多子組件的情況下Props和Emit穿透的解決方案也很多,比如和Slots一樣采用前綴的方式來(lái)分別組裝,但是這種方式較為繁瑣,這里比較推薦使用Props分組的方案,在傳入的時(shí)候就直接把
ProCard
interface ProCardProps {
title: String
cardProps: Object // 新增cardProps,用來(lái)轉(zhuǎn)發(fā)外部傳入用于card組件的props
buttonProps: Object // 新增buttonProps,用來(lái)轉(zhuǎn)發(fā)外部傳入用于button組件的props
}
// 獲取到組件所有的attributes
const attrs = useAttrs()
const props = defineProps<ProCardProps>()
// 在template中使用如下,注意替換Card組件和Button組件的v-bind為各自需要接收的props
<template>
<div class="procard-container">
<PureCard
@close="onEmitClose"
:handleClose="handleClose"
v-bind="props.cardProps"
>
<!-- 使用組裝好的cardSlots -->
<template
v-for="(slotKey, slotIndex) in cardSlots"
:key="slotIndex"
v-slot:[slotKey]
>
<slot :name="`c-${slotKey}`"></slot>
</template>
</PureCard>
<PureButton
@click="onButtonClick"
:handleClick="handleButtonClick"
v-bind="props.buttonProps"
>
<!-- 使用組裝好的buttonSlots -->
<template
v-for="(slotKey, slotIndex) in buttonSlots"
:key="slotIndex"
v-slot:[slotKey]
>
<slot :name="`b-${slotKey}`"></slot>
</template>
</PureButton>
</div>
</template>
使用方法如下
<template>
<div>
<!-- 這邊把之前的@close和:handleClose改寫如下,從cardProps傳入 -->
<ProCard
title="123"
:cardProps="{
onClose: onClose,
handleClose: handleClose
}"
>
<template #c-title>
<span>CardSlot標(biāo)題</span>
</template>
<template #c-default>
<span>CardSlot內(nèi)容</span>
</template>
<template #c-footer>
<span>CardSlot底部</span>
</template>
<template #b-default>
按鈕
</template>
</ProCard>
</div>
</template>
點(diǎn)擊Card組件關(guān)閉圖標(biāo)再單機(jī)Button組件之后效果如下

可以看到傳入的cardProps和buttonProps都起到了預(yù)期的效果
最后
希望本文可以讓你有所收獲,這是我在掘金寫的第一篇文章,希望可以幫助到大家。Slots、Emit、Props穿透的方案有很多,本文介紹的是我在項(xiàng)目中實(shí)際使用到的幾種方法,尤其是在重度依賴第三方UI組件庫(kù)的的情況下特別適用,既能很好的兼顧三方組件庫(kù)的原生Api,也能在此基礎(chǔ)上進(jìn)行增量擴(kuò)展。
最后,XDM!給摸魚的時(shí)間加鐘吧!
更多關(guān)于Slots Emit Props穿透組件封裝的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
vue關(guān)閉瀏覽器退出登錄的實(shí)現(xiàn)示例
本文主要介紹了vue關(guān)閉瀏覽器退出登錄,一般都是根據(jù)根據(jù)beforeunload和unload這兩個(gè)事件執(zhí)行的。本文就詳細(xì)的介紹一下如何實(shí)現(xiàn),感興趣的可以了解一下2021-12-12
Vue在H5 項(xiàng)目中使用融云進(jìn)行實(shí)時(shí)個(gè)人單聊通訊
這篇文章主要介紹了Vue在H5 項(xiàng)目中使用融云進(jìn)行實(shí)時(shí)個(gè)人單聊通訊,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-12-12
談?wù)剬?duì)vue響應(yīng)式數(shù)據(jù)更新的誤解
本篇文章主要介紹了談?wù)剬?duì)vue響應(yīng)式數(shù)據(jù)更新的誤解,深入了解了vue響應(yīng)式數(shù)據(jù),有興趣的可以了解一下2017-08-08
vue的Virtual Dom實(shí)現(xiàn)snabbdom解密
這篇文章主要介紹了vue的Virtual Dom實(shí)現(xiàn)- snabbdom解密,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-05-05
vue移動(dòng)UI框架滑動(dòng)加載數(shù)據(jù)的方法
這篇文章主要介紹了vue移動(dòng)UI框架滑動(dòng)加載的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-03-03
vite創(chuàng)建一個(gè)標(biāo)準(zhǔn)vue3+ts+pinia項(xiàng)目
本文主要介紹了vite創(chuàng)建一個(gè)標(biāo)準(zhǔn)vue3+ts+pinia項(xiàng)目,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-05-05
Vue Elenent實(shí)現(xiàn)表格相同數(shù)據(jù)列合并
這篇文章主要為大家詳細(xì)介紹了Vue Elenent實(shí)現(xiàn)表格相同數(shù)據(jù)列合并,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-11-11
解決vue props傳Array/Object類型值,子組件報(bào)錯(cuò)的情況
這篇文章主要介紹了解決vue props傳Array/Object類型值,子組件報(bào)錯(cuò)的情況,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-11-11

