My – blog: tuimao233. Gitee. IO/MAO – blog /

Vu3 elegant modal frame encapsulation method – a preliminary study

Vue3 elegant modal box packaging method – practice

From the introduction of the previous article, you have learned about virtual nodes and transient components. Next, we use virtual nodes and transient components to encapsulate a modal box component.

First, we need to be clear about our goal, which is the effect we want to make, which is compatible with the modal box components called by both methods

The first is used directly through template:

  <model v-model="show" title="Title" @confirm="onConfirm" @clone="onClone">I'm modal box text</model>
Copy the code

The second option is to call directly from JavaScript:

Modal({title: 'title'.content: 'I'm a modal box text'})
    .then(() = > {
    })
    .catch(() = >{})Copy the code

It can be seen from these two modes, whether in Modal, or in

.. < / model >, can be incoming parameters are consistent, therefore, component calls manner and consistent, so our first new components/Modal/props, ts, defined in external props parameter types:

/ * * modal box fixed props parameter, used to invoke the modal dialog closed | | destroyed * / success
export const modalProps = {
  // Whether to display components
  modelValue: Boolean.// When component disappears (remove instance)
  vanish: Function.// Component call success event
  resolve: Function.// Component invocation failed event
  reject: Function
}

/** The props parameter is passed in the component for the customization of the modal box */
export const componentProps = {
  // Modal box title
  title: String.// The contents of the modal box
  content: String
}

/** all Props parameters in the component, merge the parameters */
export constprops = {... modalProps, ... componentProps}Copy the code

This step is completed, we are creating components/Modal/index. The vue, import props type:

<template>
  <div></div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { props } from './props'
export default defineComponent({
  props
})
</script>
<style lang="scss" scoped></style>
Copy the code

At this point, we are defining a method to render the component from js code:

// components/Modal/utils.vue
import { Component, h, render } from "vue"

/** * Render component instance *@param * Constructor components@param Props component parameters *@returns Component instance */
export const renderInstance = (Constructor: Component, props: Record<string.any>) = > {
  // Create the component container. This step is necessary to destroy the component
  const container = document.createElement('div')

  // Add the component extinction hook in props to remove the current instance and provide the destruction method to the component
  / / there is no need to call the document. The body. RemoveChild (container. FirstElementChild)
  // Because the call to render(null, container) does the work for us
  props.vanish = () = > {
    render(null, container)
  }

  // Create virtual nodes and render components
  const vnode = h(Constructor, props)
  render(vnode, container)

  // Add child element (component) to parent element
  document.body.appendChild(container.firstElementChild)
}
Copy the code

Once the rendering method is defined, we can call the method from JS first:

import { ExtractPropTypes, ref } from "vue"
import Index from './index.vue'
import { componentProps } from './props'
import { renderInstance } from "./utils"

/** Component Props type, ExtractPropTypes converts Constructor to the corresponding value type */
type Props = ExtractPropTypes<typeof componentProps>

/** The component call resolve returns the result */
type Result = { path: string} []/** * Modal box calls method *@param props 
 * @returns {Promise}* /
export const Modal = (props: Props) = > {
  return new Promise<Result>((resolve, reject) = > {
    renderInstance(Index, {
      // In order to make the component modifiable, we need to pass in the ref
      // Note that we set this value to true in order to call up the component directly
      modelValue: ref(true),
      ...props, resolve, reject
    })
  })
}
Copy the code

In order to solve this problem, we need to pass in the modelValue Ref in the call up method.

Next we perfect the components/Modal/index. The vue component Modal logic box:

<template>
  <teleport to="body">
    <! -- Call destroy component (if any) when the after-leave component animation ends -->
    <transition name="fade" @after-leave="vanish">
      <div class="base-model__mask" v-show="show">
        <div class="base-model__content">
          <div class="base-model__title">{{ title }}</div>
          <! Insert custom slot, check whether default slot is used -->
          <! -- Render slot if used, render content if not
          <slot v-if="$slots['default']" />
          <template v-else>{{ content }}</template>
          <div class="base-model__control">
            <span @click="onConfirm">determine</span>
            <span @click="onClone">Shut down</span>
          </div>
        </div>
      </div>
    </transition>
  </teleport>
</template>
<script lang="ts">
import { defineComponent, computed, isRef, nextTick, watch } from 'vue'
import { props } from './props'
export default defineComponent({
  props,
  setup: (props, { emit }) = > {
    // Two-way proxy for the data displayed by the component
    const modelValue = computed({
      get: () = > <boolean>props.modelValue,
      set: () = > emit('update:modelValue')})// The Modal method call passed to props cannot be modified by emit
    // if the input is a ref, use it directly
    const show = isRef(props.modelValue) ? props.modelValue : modelValue

    // If initialized to true, toggle the state so that the animation appears normally
    if (show.value) {
      show.value = false
      nextTick(() = > show.value = true)}// Close the event, call Reject, and call the Clone event again in order to be compatible with using the component directly on the template
    const onClone = () = >{ props.reject? .() emit('clone')
      show.value = false
    }

    // Determine the event, call resolve, and call the Confirm event once, in order to use the component directly on compatible templates
    const onConfirm = () = >{ props.resolve? .() emit('confirm')
      show.value = false
    }

    return { show, onConfirm, onClone }
  }
})
</script>
<style lang="scss" scoped>
.base-model__mask {
  position: fixed;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0.0.0.0.4);
}
.base-model__content {
  position: absolute;
  border-radius: 20px;
  width: 600px;
  height: 300px;
  background-color: #ffffff;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  padding: 20px;
}
.base-model__control {
  position: absolute;
  right: 0;
  bottom: 20px;
  span {
    margin-right: 20px; }}/* Component animation start */
.fade-enter-active..fade-leave-active {
  transition: opacity 0.2 s;
}
.fade-enter-from..fade-leave-to {
  opacity: 0;
}
.fade-enter-top..fade-leave-from {
  opacity: 1;
}
/* Component animation end */
</style>
Copy the code

At this point, we can test if the component call works, for example, by using the template component:

<template>
  <img alt="Vue logo" src="./assets/logo.png" @click="show = true" />
  <modal @clone="onClone" @confirm="onConfirm" v-model="show" title="I am the title." >La la la LA I am custom content</modal>
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'
import Modal from './components/Modal/index.vue';

export default defineComponent({
  components: { Modal },
  setup: () = > {
    const show = ref(false)
    const onClone = () = > {
      console.log('Modal box click close')}const onConfirm = () = > {
      console.log('Modal box click ok')}return { onClone, onConfirm, show }
  }
})
</script>
Copy the code

In the test, call the modal box via JavaScript:

<template>
  <img alt="Vue logo" src="./assets/logo.png" @click="onClick" />
</template>

<script lang="ts">
import { defineComponent, ref } from 'vue'
import { Modal } from './components/Modal';

export default defineComponent({
  components: {},setup: () = > {
    const onClick = () = > {
      Modal({title: 'I'm the title.'.content: 'I am content ~~~'})
        .then(() = > {
          console.log('Component call successful')
        })
        .catch(() = > {
          console.log('Component call failed')})}return {onClick}
  }
})
</script>
Copy the code

Here, the basic logic of the Modal dialog, and on this basis, the perfect Modal dialog can be based on demand and custom content, the method also can be secondary packaging el – dialog component, just need to components/Modal/index. The vue logic can be modified, the next article, We are building on this foundation to make the components fully competent to the business requirements.