使用Vue手寫(xiě)一個(gè)對(duì)話(huà)框
寫(xiě)在前面
相信大家之前都寫(xiě)過(guò)一些組件,尤其是這樣的彈窗組件,沒(méi)吃過(guò)豬肉還沒(méi)見(jiàn)過(guò)豬跑嘛 哈哈哈~
有人可能會(huì)說(shuō),為什么要自己寫(xiě),我就用ant-design或者餓了么的。sorry,我要的彈窗UI跟組件庫(kù)的相差太遠(yuǎn)了,而且對(duì)話(huà)框組件通常都掛載在body下,要改樣式的話(huà)得一個(gè)個(gè)去寫(xiě)全局的樣式,會(huì)全局覆蓋掉組件庫(kù)的css…… 微調(diào)改樣式就還好。如果組件庫(kù)的對(duì)話(huà)框dom結(jié)構(gòu)跟你想要的差了十萬(wàn)八千里,那也是佛祖難救……
基本實(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è)語(yǔ)法糖,用起來(lái)真香
組件中使用
下面給出一個(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)這里不寫(xiě)最好
// 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)這里不寫(xiě)最好
// document.body.appendChild(modalWarp)
}使用方法
下面給出一個(gè)基本示例
showPosDialog({
title: '是否移除該商品?',
onConfirm() {
emits('delete')
},
})
這樣,我們就實(shí)現(xiàn)了一個(gè)組件既可以在template中使用,也可以在任何js中使用啦
初始不渲染
不知道大家有沒(méi)有注意到,組件庫(kù)的對(duì)話(huà)框在第一次打開(kāi)之前,是沒(méi)有掛載到body節(jié)點(diǎn)下的。上面我們封裝的組件,如果有100個(gè)對(duì)話(huà)框,頁(yè)面一開(kāi)始就會(huì)在body下掛載100個(gè)節(jié)點(diǎn),且都是實(shí)例化完成后的,增加了性能上的開(kāi)銷(xiāo)
投石問(wèn)路
這可咋整,百度也不知道怎么問(wèn)。一時(shí)間沒(méi)有好的思路,我就去down一個(gè)ant-design-vue的源碼看看。不得不說(shuō),這個(gè)項(xiàng)目是一層組件套一層,太雞兒復(fù)雜了。功夫不負(fù)有心人,我在components/_util/Portal.tsx文件第69行看到了這么一行代碼
return () => {
if (!shouldRender.value) return null;
if (isSSR) {
return slots.default?.();
}
//沒(méi)錯(cuò),就是這行!
return container ? <Teleport to={container} v-slots={slots}></Teleport> : null;
};
ant-design設(shè)計(jì)思路比較復(fù)雜,咱就不過(guò)多深究了,反正實(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的話(huà)需要監(jiān)聽(tīng)第一次打開(kāi)
if (!props.open) {
// 打開(kāi)過(guò)一次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用起來(lái)比較順手,寫(xiě)的時(shí)候注意把props、slot、attrs這些透?jìng)飨氯ゼ纯?/p>
vNode
其實(shí)在A(yíng)PI方式調(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)過(guò)程沒(méi)有那么順暢,但也算是填補(bǔ)一部分技術(shù)空白吧。如果大佬有更好的想法,歡迎在評(píng)論區(qū)交流呀
別人都用hooks我為什么用工具函數(shù)
反正各有各的看法吧,用hooks可以方便用vue的響應(yīng)式數(shù)據(jù)、生命周期鉤子等等,但是只能在vue組件中去調(diào)用。我的實(shí)現(xiàn)沒(méi)有用到vue的這些東西,所以就寫(xiě)成工具函數(shù)了,在哪都可以調(diào)用。
個(gè)人覺(jué)得,沒(méi)有用到vue這些東西,沒(méi)必要強(qiáng)制hooks化。
再看vue的hooks使用,實(shí)際上就是在構(gòu)建之初實(shí)例化一次,后續(xù)的"消費(fèi)"在于調(diào)用返回的函數(shù)。像我這種API的對(duì)話(huà)框,就是創(chuàng)建的時(shí)候?qū)嵗淮危P(guān)閉后就銷(xiāo)毀了。沒(méi)有復(fù)用一說(shuō),也就沒(méi)必要寫(xiě)成hooks了
至于為什么不復(fù)用,如果同時(shí)存在兩個(gè)API調(diào)起的對(duì)話(huà)框,你只有一個(gè)實(shí)例(復(fù)用方案),那無(wú)法滿(mǎn)足需求了
總結(jié)
相比于我之前封裝的組件,我覺(jué)得對(duì)話(huà)框?qū)儆诼杂刑厥獾慕M件吧。
- 得掛載body下防止樣式層級(jí)受影響
- 需要支持API方式調(diào)用
- 初始不渲染
- 支持vNode傳參
到此這篇關(guān)于使用Vue手寫(xiě)一個(gè)對(duì)話(huà)框的文章就介紹到這了,更多相關(guān)Vue對(duì)話(huà)框內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
vue3組合式API獲取子組件屬性和方法的代碼實(shí)例
這篇文章主要為大家詳細(xì)介紹了vue3組合式API獲取子組件屬性和方法的代碼實(shí)例,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以跟隨小編一起學(xué)習(xí)一下2024-01-01
el-select自定義指令實(shí)現(xiàn)觸底加載分頁(yè)請(qǐng)求options數(shù)據(jù)(完整代碼和接口可直接用)
某些情況下,下拉框需要做觸底加載,發(fā)請(qǐng)求,獲取option的數(shù)據(jù),下面給大家分享el-select自定義指令實(shí)現(xiàn)觸底加載分頁(yè)請(qǐng)求options數(shù)據(jù)(附上完整代碼和接口可直接用),感興趣的朋友參考下吧2024-02-02
Vue監(jiān)控路由與路由參數(shù), 刷新當(dāng)前頁(yè)面數(shù)據(jù)的方法匯總
這篇文章主要介紹了Vue監(jiān)控路由與路由參數(shù), 刷新當(dāng)前頁(yè)面數(shù)據(jù)的幾種方法,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2023-10-10
vue3中使用v-model實(shí)現(xiàn)父子組件數(shù)據(jù)同步的三種方案
這篇文章主要介紹了vue3中使用v-model實(shí)現(xiàn)父子組件數(shù)據(jù)同步的三種方案,如果只有一個(gè)匿名v-model的傳遞的話(huà),可以使用vue3.3新添加的編譯宏,defineModel來(lái)使用,每種方案結(jié)合示例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2023-10-10
在vue中實(shí)現(xiàn)PDF文件流預(yù)覽功能
這篇文章主要為大家詳細(xì)介紹如何在vue中實(shí)現(xiàn)PDF文件流預(yù)覽功能,文中的實(shí)現(xiàn)步驟講解詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考價(jià)值,需要的可以參考一下2023-12-12

