使用Vue手寫一個(gè)對(duì)話框
寫在前面
相信大家之前都寫過一些組件,尤其是這樣的彈窗組件,沒吃過豬肉還沒見過豬跑嘛 哈哈哈~
有人可能會(huì)說,為什么要自己寫,我就用ant-design或者餓了么的。sorry,我要的彈窗UI跟組件庫的相差太遠(yuǎn)了,而且對(duì)話框組件通常都掛載在body下,要改樣式的話得一個(gè)個(gè)去寫全局的樣式,會(huì)全局覆蓋掉組件庫的css…… 微調(diào)改樣式就還好。如果組件庫的對(duì)話框dom結(jié)構(gòu)跟你想要的差了十萬八千里,那也是佛祖難救……
基本實(shí)現(xiàn)
<template> <Teleport to="body"> <div class="popDialogMask" :style="{ zIndex: props.zIndex }" v-show="modalOpen" @click="props.maskClosable && (modalOpen = false)" v-bind="$attrs" > <div class="popDialogContent" @click.stop :style="{ width: props.width + 'px' }"> <slot name="title"> <div class="title"> <div class="i-carbon:warning mr-2 color-#FF9A42" /> {{ props.title }} </div> </slot> <slot name="content"> <div class="content"></div> </slot> <slot name="footer"> <div class="footer"> <a-button type="primary" ghost @click="handleEdit('cancel')" v-if="cancelButtonVisible" >{{ props.cancelText }}</a-button > <a-button type="primary" @click="handleEdit('confirm')" :block="!cancelButtonVisible">{{ props.okText }}</a-button> </div> </slot> </div> </div> </Teleport> </template> <script lang="ts" setup> import { type IDiaLogProps } from './types' const modalOpen = defineModel<boolean>('open') const props = withDefaults(defineProps<IDiaLogProps>(), { title: '', maskClosable: false, cancelButtonVisible: true, zIndex: 2000, okText: '確定', cancelText: '取消', width: 640, }) const emits = defineEmits(['confirm', 'cancel']) const handleEdit = (type: Parameters<typeof emits>[0]) => { modalOpen.value = false emits(type) } </script> <style lang="scss" scoped> .popDialogMask { @apply fixed top-0 bottom-0 left-0 right-0 flex justify-center items-center; background-color: rgba(0, 0, 0, 0.45); .popDialogContent { @apply bg-white rounded-3xl p-12 box-border; .title { @apply text-3xl flex justify-center items-center font-bold; } .content { @apply h-26; } .footer { @apply flex justify-between; :deep(.ant-btn) { @apply p-x-22.5 rounded-20 p-y-0 text-3xl; height: 80px; line-height: 80px; } } } } </style>
順便我要講一下vue的defineModel這個(gè)語法糖,用起來真香
組件中使用
下面給出一個(gè)基本示例
<pos-dialog v-model:open="posDialogVisible" title="我是title" :cancelButtonVisible="false" > <template #content> <div class="my-13 text-center">content</div> </template> </pos-dialog>
API方式調(diào)用
下面我們?cè)趗tils中把組件引入,導(dǎo)出一個(gè)工具函數(shù)給我們后續(xù)API方式"食用"
export const showPosDialog = (option: typeof PosDialog.props) => { const modalWarp = document.createElement('div') const destroy = () => { // eslint-disable-next-line no-use-before-define modalInstance.unmount() //因?yàn)橛昧薚eleport 我發(fā)現(xiàn)這里不寫最好 // document.body.removeChild(modalWarp) } const modalInstance = createApp(PosDialog, { ...option, open: true, //AOP onConfirm() { option.onConfirm && option.onConfirm() destroy() }, onCancel() { option.onCancel && option.onCancel() destroy() }, }) modalInstance.mount(modalWarp) //因?yàn)橛昧薚eleport 我發(fā)現(xiàn)這里不寫最好 // document.body.appendChild(modalWarp) }
使用方法
下面給出一個(gè)基本示例
showPosDialog({ title: '是否移除該商品?', onConfirm() { emits('delete') }, })
這樣,我們就實(shí)現(xiàn)了一個(gè)組件既可以在template中使用,也可以在任何js中使用啦
初始不渲染
不知道大家有沒有注意到,組件庫的對(duì)話框在第一次打開之前,是沒有掛載到body節(jié)點(diǎn)下的。上面我們封裝的組件,如果有100個(gè)對(duì)話框,頁面一開始就會(huì)在body下掛載100個(gè)節(jié)點(diǎn),且都是實(shí)例化完成后的,增加了性能上的開銷
投石問路
這可咋整,百度也不知道怎么問。一時(shí)間沒有好的思路,我就去down一個(gè)ant-design-vue的源碼看看。不得不說,這個(gè)項(xiàng)目是一層組件套一層,太雞兒復(fù)雜了。功夫不負(fù)有心人,我在components/_util/Portal.tsx文件第69行看到了這么一行代碼
return () => { if (!shouldRender.value) return null; if (isSSR) { return slots.default?.(); } //沒錯(cuò),就是這行! return container ? <Teleport to={container} v-slots={slots}></Teleport> : null; };
ant-design設(shè)計(jì)思路比較復(fù)雜,咱就不過多深究了,反正實(shí)現(xiàn)思路是有了。搞個(gè)組件包一層。如果是第一次且綁定值為false那就返回一個(gè)null
具體實(shí)現(xiàn)
import { type IDiaLogProps } from './types' import Dialog from './Dialog.vue' export default defineComponent<IDiaLogProps & { open: boolean }>({ name: 'PosDialog', inheritAttrs: false, props: Dialog.props, setup(props, { attrs, slots }) { const isFirstRender = ref(true) // 初始值為false的話需要監(jiān)聽第一次打開 if (!props.open) { // 打開過一次dialog 后,將 isFirstRender 設(shè)置為 false const unWatch = watch( () => props.open, (val) => { if (val) { isFirstRender.value = false unWatch() } } ) } else { isFirstRender.value = false } return () => { return isFirstRender.value ? null : <Dialog v-slots={slots} {...props} {...attrs} /> } }, })
干這種活還是用tsx用起來比較順手,寫的時(shí)候注意把props、slot、attrs這些透?jìng)飨氯ゼ纯?/p>
vNode
其實(shí)在API方式調(diào)用時(shí),我們還可以傳vNode給組件。
修改組件
修改組件中需要支持vNode的插槽
<component :is="props.title" v-if="isVNode(props.title)"></component> <slot name="title" v-else> <div class="title"> <div class="i-carbon:warning mr-2 color-#FF9A42" /> {{ props.title }} </div> </slot>
使用vNode
showPosDialog({ // title: '是否移除該商品?', title: <div>vNode:是否移除該商品?</div>, onConfirm() { emits('delete') }, })
思考
實(shí)現(xiàn)過程沒有那么順暢,但也算是填補(bǔ)一部分技術(shù)空白吧。如果大佬有更好的想法,歡迎在評(píng)論區(qū)交流呀
別人都用hooks我為什么用工具函數(shù)
反正各有各的看法吧,用hooks可以方便用vue的響應(yīng)式數(shù)據(jù)、生命周期鉤子等等,但是只能在vue組件中去調(diào)用。我的實(shí)現(xiàn)沒有用到vue的這些東西,所以就寫成工具函數(shù)了,在哪都可以調(diào)用。
個(gè)人覺得,沒有用到vue這些東西,沒必要強(qiáng)制hooks化。
再看vue的hooks使用,實(shí)際上就是在構(gòu)建之初實(shí)例化一次,后續(xù)的"消費(fèi)"在于調(diào)用返回的函數(shù)。像我這種API的對(duì)話框,就是創(chuàng)建的時(shí)候?qū)嵗淮危P(guān)閉后就銷毀了。沒有復(fù)用一說,也就沒必要寫成hooks了
至于為什么不復(fù)用,如果同時(shí)存在兩個(gè)API調(diào)起的對(duì)話框,你只有一個(gè)實(shí)例(復(fù)用方案),那無法滿足需求了
總結(jié)
相比于我之前封裝的組件,我覺得對(duì)話框?qū)儆诼杂刑厥獾慕M件吧。
- 得掛載body下防止樣式層級(jí)受影響
- 需要支持API方式調(diào)用
- 初始不渲染
- 支持vNode傳參
到此這篇關(guān)于使用Vue手寫一個(gè)對(duì)話框的文章就介紹到這了,更多相關(guān)Vue對(duì)話框內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue.js中window.onresize的超詳細(xì)使用方法
這篇文章主要給大家介紹了關(guān)于vue.js中window.onresize的超詳細(xì)使用方法,window.onresize 是直接給window的onresize屬性綁定事件,只能有一個(gè),文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下2023-12-12vue項(xiàng)目部署到Apache服務(wù)器中遇到的問題解決
這篇文章主要介紹了vue項(xiàng)目部署到Apache中遇到的問題解決,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2018-08-08vue2.0構(gòu)建單頁應(yīng)用最佳實(shí)戰(zhàn)
這篇文章主要為大家分享了vue2.0構(gòu)建單頁應(yīng)用最佳實(shí)戰(zhàn)案例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-04-04vue-class-setup?編寫?class?風(fēng)格組合式API
這篇文章主要為大家介紹了vue-class-setup?編寫?class?風(fēng)格組合式API,支持Vue2和Vue3,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09vue項(xiàng)目中vant tab改變標(biāo)簽顏色方式
這篇文章主要介紹了vue項(xiàng)目中vant tab改變標(biāo)簽顏色方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。2022-04-04Vue狀態(tài)機(jī)的開啟與停止操作詳細(xì)講解
Vuex是專門為Vuejs應(yīng)用程序設(shè)計(jì)的狀態(tài)管理工具,這篇文章主要給大家介紹了關(guān)于Vuex狀態(tài)機(jī)快速了解與實(shí)例應(yīng)用的相關(guān)資料,需要的朋友可以參考下2023-01-01vue使用axios跨域請(qǐng)求數(shù)據(jù)問題詳解
這篇文章主要為大家詳細(xì)介紹了vue使用axios跨域請(qǐng)求數(shù)據(jù)的問題,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-10-10