前言
业务场景:封装了一个卡片组件,里面有一个相关逻辑,需要出现一个Modal(弹层)
当前实现:当前把 Modal 写到了卡片中
<template>
<div class="card">
<div @click="handleClickOpen">出弹窗</div>
</div>
<Model v-model="show" />
</template>
<script lang="ts" setup>
import { ref } from 'vue'
import Modal from './Modal.vue'
const show = ref(false)
function handleClickOpen() {
show.value = true
}
</script>
<style lang="less" scoped>
.card {
width: 150px;
height: 80px;
border-radius: 20px;
background-color: rgba(0, 0, 0, 0.3);
margin: auto;
margin-bottom: 10px;
}
</style>
就导致每次渲染卡片的时候也会渲染 Modal
期望:不要每次都渲染这个Modal
实现
通过单例的方式包裹一层,并以 api 的方式调用
<template>
<div class="card">
<div @click="handleClickOpen">出弹窗</div>
</div>
</template>
<script lang="ts" setup>
import ModalFn from './ModalFn'
function handleClickOpen() {
ModalFn()
}
</script>
<style lang="less" scoped>
.card {
width: 150px;
height: 80px;
border-radius: 20px;
background-color: rgba(0, 0, 0, 0.3);
margin: auto;
margin-bottom: 10px;
}
</style>
ModalFn 实现
首先,我们需要一个 wrapper 来包裹我们的 Modal,这里我们使用 h 方法来创建
const wrapper = {
setup() {
const show = ref(true)
return () =>
h(Modal, {
modelValue: show.value,
'onUpdate:modelValue': (val: boolean) => {
show.value = val
}
})
}
}
创建一个全局变量 instance ,如果没有就创建实例,有的话,直接展示
if (!instance) {
// 创建实例
} else {
// 展示实例
}
那么我们如何创建组件实例呢,我们用到了 createApp 这个方法
export function mountComponent(RootComponent) {
const app = createApp(RootComponent)
const root = document.createElement('div')
document.body.appendChild(root)
return {
instance: app.mount(root),
unmount() {
app.unmount()
document.body.removeChild(root)
}
}
}
还有一个关键点,我们需要把再次展示的变量挂在到实例上,这样可以让我们的 modal 重新展示
const instance = getCurrentInstance()
function togger(val: boolean) {
show.value = val
}
Object.assign(instance.proxy, { show, togger })
展示实例
instance.togger(true)
ModalFn 的整体代码
import { createApp, getCurrentInstance, h, ref } from 'vue'
import Modal from './Modal.vue'
let instance
export default function ModalFn() {
const wrapper = {
setup() {
const show = ref(true)
const instance = getCurrentInstance()
function togger(val: boolean) {
show.value = val
}
Object.assign(instance.proxy, { show, togger })
return () =>
h(Modal, {
modelValue: show.value,
'onUpdate:modelValue': (val: boolean) => {
show.value = val
}
})
}
}
if (!instance) {
const component = mountComponent(wrapper)
instance = component.instance
} else {
instance.togger(true)
}
}
export function mountComponent(RootComponent) {
const app = createApp(RootComponent)
const root = document.createElement('div')
document.body.appendChild(root)
return {
instance: app.mount(root),
unmount() {
app.unmount()
document.body.removeChild(root)
}
}
}
总结
这样就只会挂载一次了