使用单例模式调用Modal

401 阅读2分钟

前言

业务场景:封装了一个卡片组件,里面有一个相关逻辑,需要出现一个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

image.png

期望:不要每次都渲染这个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)
    }
  }
}

总结

这样就只会挂载一次了

image.png