Slots Emit和Props穿透组件封装实现摸鱼加钟
脚本之家 / 编程助手:解决程序员“几乎”所有问题!
脚本之家官方知识库 → 点击立即使用
👀背景
组内多人共同开发时免不了基于某UI库二次封装组件来适应项目业务场景的情况,但不知道大家有没有遇到过需要兼容部分或者穿透子组件全部Props
或者Slots
的情况,这种时候如果针对每一个Props或者Slots去单独处理穿透不仅费时费力而且代码会越来越臃肿难以维护,所以想在这里通过一个简单的例子来对比一下Slots、Props、Emit的各种穿透方案
🐱🏍准备工作
首先新建我们需要用到的子组件,如下
Card.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | < template > < div class = "card-container" > < div @ click = "handleClose" class = "card-close" > <!-- 先用X来代替 --> < span >X</ span > </ div > < div class = "card-title" > < slot name = "title" > <!-- 默认使用props作为title,有slot则优先slot --> {{props.title}} </ slot > </ div > < div class = "card-content" > < slot > <!-- content这里也是,一切都以slot优先 --> {{props.content}} </ slot > </ div > < div class = "card-footer" > < slot name = "footer" > <!-- footer这里也是,一切都以slot优先 --> {{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']) // 响应点击事件 const handleClose = () => { // 这边演示方便,直接调props之后跟上emit调用 props.handleClose && props.handleClose() emits('close') } </ script > ...css部分略过 |
再来准备一个Button.vue
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | < 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 > |
以及我们需要实现的ProCard.vue
Slots穿透方案-单子组件
使用Vue
提供的Dynamic directive arguments
结合v-slot
指令 Dynamic directive arguments部分文档链接 在单子组件的情况下穿透Slot比较简单,不需要考虑太多的Slot覆盖问题,只需要关注封装组件自身Slot命名即可,如有命名重复情况可参考多子组件方案解决,比如下面这个ProCard.vue
,只用到了Card
组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | < 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 > |
使用
1 2 3 4 5 6 7 8 9 | < template > < div > < ProCard > < template #title> < span >CardSlot标题</ span > </ template > </ ProCard > </ div > </ template > |
效果
Slots穿透方案-多子组件
通常我们封装业务组件时一般不至于一个子组件,但多个子组件的情况下就要特别注意Slot命名情况了,这边分享一个在平时开发时我们选择的一个方案:使用不同前缀来区分不同slot,props也是同理。在ProCard.vue
中我们加入一个Button
组件,协商约定c-xxx
为Card
组件Slot,b-xxx
为Button
组件Slot,这样在经过分解之后就可以区分出应该往哪个组件穿透Slot了。
在ProCard.vue
中取的所有slots并且理好各个组件所需slots
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | // 首先还是取到所有Slots的key const slots = Object.keys(useSlots()) // 定义一个buttonSlots,用来组装需要用到的Button组件的slots const buttonSlots = ref<string[]>([]) // 定义一个cardSlots,用来组装需要用到的Card组件的slots const cardSlots = ref<string[]>([]) // 找出各自组件需要的slot组装好push进去就可以在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)) } } |
接下来就可以在template中直接使用了
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | < 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
组件来看一下效果吧
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | < template > < div > < ProCard title = "123" > < template #c-title> < span >CardSlot标题</ span > </ template > < template #c-default> < span >CardSlot内容</ span > </ template > < template #c-footer> < span >CardSlot底部</ span > </ template > < template #b-default> 按钮 </ template > </ ProCard > </ div > </ template > |
成功实现了多组件Slots穿透
Props和Emit穿透方案-单子组件
Props和Emit的穿透方式与Slots的方案类似,使用v-bind
直接绑定组件Attributes
是最方便的穿透方式,但缺点也很明细,直接v-bind
所有Attributes
可能会导致命名重复所带来的各种连锁问题,如果像上文slots
一样通过前缀来区分组装又有点繁琐,所以如果是多子组件的情况下推荐使用下面的props+v-bind
方案。
单子组件这边在ProCard
中使用useAttrs
来得到子组件上所有的attributes,再使用v-bind
直接转发到ProCard
的子组件,这样就可以直接穿透Props和Emit了非常方便好用
template中转发到子组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | < 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 > |
父组件调用ProCard
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | < 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标题</ span > </ template > < template #c-default> < span >CardSlot内容</ span > </ template > < template #c-footer> < span >CardSlot底部</ span > </ template > < template #b-default> 按钮 </ template > </ ProCard > </ template > |
看一下实际效果
点击一下右上角关闭按钮
可以看到成功穿透了Emit和Props并且被子组件给执行了
Props和Emit穿透方案-多子组件
多子组件的情况下Props和Emit穿透的解决方案也很多,比如和Slots一样采用前缀的方式来分别组装,但是这种方式较为繁琐,这里比较推荐使用Props分组的方案,在传入的时候就直接把
ProCard
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | interface ProCardProps { title: String cardProps: Object // 新增cardProps,用来转发外部传入用于card组件的props buttonProps: Object // 新增buttonProps,用来转发外部传入用于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> |
使用方法如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | < template > < div > <!-- 这边把之前的@close和:handleClose改写如下,从cardProps传入 --> < ProCard title = "123" :cardProps="{ onClose: onClose, handleClose: handleClose }" > < template #c-title> < span >CardSlot标题</ span > </ template > < template #c-default> < span >CardSlot内容</ span > </ template > < template #c-footer> < span >CardSlot底部</ span > </ template > < template #b-default> 按钮 </ template > </ ProCard > </ div > </ template > |
点击Card
组件关闭图标再单机Button
组件之后效果如下
可以看到传入的cardProps
和buttonProps
都起到了预期的效果
最后
希望本文可以让你有所收获,这是我在掘金写的第一篇文章,希望可以帮助到大家。Slots、Emit、Props
穿透的方案有很多,本文介绍的是我在项目中实际使用到的几种方法,尤其是在重度依赖第三方UI组件库的的情况下特别适用,既能很好的兼顾三方组件库的原生Api,也能在此基础上进行增量扩展。
最后,XDM!给摸鱼的时间加钟吧!
更多关于Slots Emit Props穿透组件封装的资料请关注脚本之家其它相关文章!
微信公众号搜索 “ 脚本之家 ” ,选择关注
程序猿的那些事、送书等活动等着你
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若内容造成侵权/违法违规/事实不符,请将相关资料发送至 reterry123@163.com 进行投诉反馈,一经查实,立即处理!
相关文章
Vue中@click.stop与@click.prevent、@click.native使用
这篇文章主要介绍了Vue中@click.stop与@click.prevent、@click.native使用方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教2024-08-08Vue中使用localStorage存储token并设置时效
这篇文章主要为大家介绍了Vue中使用localStorage存储token并设置时效,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪2023-06-06
最新评论