Write in front
I believe everyone has written some components before, especially such pop-up components. I have never eaten pork and haven't seen pigs running. Hahaha~
Some people may say, why do I have to write it myself, I just use ant-design or Ele.me. sorry, the pop-up UI I want is too different from the component library, and the dialog components are usually mounted under the body. If you want to change the style, you have to write the global style one by one, which will globally overwrite the css of the component library... It's okay to fine-tune the style. If the dom structure of the component library dialog box is thousands of miles away from what you want, it will be difficult for Buddha to save it...
Basic implementation
<template> <Teleport to="body"> <div class="popDialogMask" :style="{ zIndex: }" v-show="modalOpen" @click=" && (modalOpen = false)" v-bind="$attrs" > <div class="popDialogContent" @ :style="{ width: + 'px' }"> <slot name="title"> <div class="title"> <div class="i-carbon:warning mr-2 color-#FF9A42" /> {{ }} </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" >{{ }}</a-button > <a-button type="primary" @click="handleEdit('confirm')" :block="!cancelButtonVisible">{{ }}</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: 'Sure', cancelText: 'Cancel', width: 640, }) const emits = defineEmits(['confirm', 'cancel']) const handleEdit = (type: Parameters<typeof emits>[0]) => { = 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>
By the way, I want to talk about the syntax candy of vue's defineModel, which is really delicious to use.
Used in components
Here is a basic example
<pos-dialog v-model:open="posDialogVisible" title="I'm title" :cancelButtonVisible="false" > <template #content> <div class="my-13 text-center">content</div> </template> </pos-dialog>
API call
Next, we introduce the component in utils and export a tool function for us to "eat" the subsequent API method.
export const showPosDialog = (option: typeof ) => { const modalWarp = ('div') const destroy = () => { // eslint-disable-next-line no-use-before-define () //Because I have used Teleport, I found it is best not to write here // (modalWarp) } const modalInstance = createApp(PosDialog, { ...option, open: true, //AOP onConfirm() { && () destroy() }, onCancel() { && () destroy() }, }) (modalWarp) //Because I have used Teleport, I found it is best not to write here // (modalWarp) }
How to use
Here is a basic example
showPosDialog({ title: 'Did you remove the item? ', onConfirm() { emits('delete') }, })
In this way, we implement a component that can be used in template or in any js
No rendering at the beginning
I don’t know if you have noticed that the dialog box of the component library was not mounted under the body node before the first time it was opened. If the components we encapsulated above have 100 dialog boxes, the page will mount 100 nodes on the body at the beginning, and they are all instantiated, which increases performance overhead.
Ask for the way
Baidu doesn't know how to ask what to do about this. I didn't have a good idea for a while, so I went down ant-design-vue source code to check it out. I have to say that this project is a layer of components and layers, which is too complicated. Hard work pays off. I saw this line of code on line 69 of components/_util/ file
return () => { if (!) return null; if (isSSR) { return ?.(); } //Yes, this is the job! return container ? <Teleport to={container} v-slots={slots}></Teleport> : null; };
The design idea of ant-design is quite complicated, so we won’t go into it too much, anyway, there is an implementation idea. Create a component package. If it is the first time and the binding value is false, then return a null
Specific implementation
import { type IDiaLogProps } from './types' import Dialog from './' export default defineComponent<IDiaLogProps & { open: boolean }>({ name: 'PosDialog', inheritAttrs: false, props: , setup(props, { attrs, slots }) { const isFirstRender = ref(true) // If the initial value is false, you need to listen to the first time you open it if (!) { // After opening the dialog once, set isFirstRender to false const unWatch = watch( () => , (val) => { if (val) { = false unWatch() } } ) } else { = false } return () => { return ? null : <Dialog v-slots={slots} {...props} {...attrs} /> } }, })
It is easier to use tsx to do this kind of work. When writing, please pass it on to props, slots, and attrs.
vNode
In fact, when calling the API method, we can also pass vNode to the component.
Modify components
Modify the slots that need to support vNode in the component
<component :is="" v-if="isVNode()"></component> <slot name="title" v-else> <div class="title"> <div class="i-carbon:warning mr-2 color-#FF9A42" /> {{ }} </div> </slot>
Using vNode
showPosDialog({ // title: 'Did you remove the product? ', title: <div>vNode:Whether to remove the product?</div>, onConfirm() { emits('delete') }, })
think
The implementation process is not that smooth, but it can be considered filling some technical gaps. If the boss has better ideas, please feel free to communicate in the comment section
Why do I use tool functions if others use hooks?
Anyway, everyone has their own opinions. Using hooks can facilitate vue's responsive data, life cycle hooks, etc., but they can only be called in vue components. My implementation does not use these things in vue, so it is written as a tool function and can be called anywhere.
Personally, I think that there is no need to force hooks to do so.
Let’s look at the use of vue hooks. In fact, it is instantiated once at the beginning of the construction. The subsequent "consumption" lies in calling the returned function. Dialogs for APIs like me are instantiated once when created and destroyed after closing. Without reuse, there is no need to write hooks
As for why not reuse, if there are two APIs at the same time, you only have one instance (reuse scheme), then it cannot meet the needs.
Summarize
Compared to the components I encapsulated before, I think the dialog box is a slightly special component.
- You must mount the body to prevent the style level from being affected
- Need to support API calls
- No rendering at the beginning
- Support vNode parameter transfer
This is the end of this article about using Vue to write a dialog box. For more related content on Vue dialog box, please search for my previous articles or continue browsing the related articles below. I hope everyone will support me in the future!