Vue3: Optimization of multi-Modal box business logic code

background

The following scenarios are often encountered in development. Click the button to control the display and hide of the pop-up box through variable Visible:

export default defineComponent({
  setup() {
    const visible = ref(false);
    return {
      visible,
    };
  },
  render() {
    return (
      <>
        <Button
          onClick={()= >{ this.visible = true; }} > Displays the pop-up box</Button>
        <MyModal
          visible={visible}
          onClose={()= >{ this.visible = false; }} / ></>); }});Copy the code

However, as the business continues to iterate, there may be multiple Modal boxes on a page. At this time, how should we deal with them more elegantly? Such as:

export default defineComponent({
  setup() {
    const visible1 = ref(false);
    const visible2 = ref(false);
    const visible3 = ref(false);
    return {
      visible1,
      visible2,
      visible3,
    };
  },
  render() {
    return (
      <>
        <Button
          onClick={()= >{ this.visible1 = true; }} > Display dialog box 1</Button>
        <Button
          onClick={()= >{ this.visible2 = true; }} > Display dialog box 2</Button>
        <Button
          onClick={()= >{ this.visible2 = true; }} > Display dialog box 3</Button>
        <MyModal1
          visible={visible1}
          onClose={()= >{ this.visible1 = false; }} / ><MyModal2
          visible={visible2}
          onClose={()= >{ this.visible2 = false; }} / ><MyModal3
          visible={visible3}
          onClose={()= >{ this.visible3 = false; }} / ></>); }});Copy the code

In the case of multiple Modal boxes, this method obviously doesn’t look elegant. Verbose code is not concise enough.

We usually should be so development, the question is whether there is a better way?

Train of thought

This is certainly the case with the declarative component development pattern. But if we can call the component functionally, it might be a good choice. The pseudocode is as follows:

export default defineComponent({
  render() {
    return (
      <>
        <Button
          onClick={()= >{ MyModal1({}).then((data) => {}); }} > Display dialog box 1</Button>
        <Button
          onClick={()= >{ MyModal2({}).then((data) => {}); }} > Display dialog box 2</Button>
        <Button
          onClick={()= >{ MyModal3({}).then((data) => {}); }} > Display dialog box 3</Button>
      </>); }});Copy the code

This way, at least, is more logical and readable. This is the way I recommend and like it. I used react to encapsulate basic components. Now switch to the VUE3 technology stack and try it again.

How to implement

First paste the code, the basic package component implementation (logic see code comments):

import { createVNode, render, VNodeTypes } from "vue";

export async function DynamicModal(component: VNodeTypes, props: object) {
  return new Promise((resolve) = > {
    1️ ⃣// Create a parent package object
    const container = document.createElement("div");
    2️ ⃣// Create vue Node instance and pass props(force pass onClose method)
    constvm = createVNode(component, { ... props,onClose: (params: unknown) = > {
        5️ ⃣// The onClose method is called when the child component is destroyed to remove the responding DOM node
        document.body.removeChild(
          document.querySelector(".ant-modal-root")? .parentNodeas Node
        );
        document.body.removeChild(container);
        6️ ⃣// Returns the data passed by the child componentresolve(params); }});3️ ⃣// Render the vue Node instance into the wrap object
    render(vm, container);

    4️ ⃣// Insert the wrap object into the body
    document.body.appendChild(container);
  });
}
Copy the code

How to use:

// CellModal.tsx
// Define the component
const CellModal = defineComponent({
  props: {
    onClose: {
      type: Function as PropType<(param: unknown) = > void>,
      required: true,},props1: {
      type: String as PropType<string>,
      required: true,},props2: {
      type: Object as PropType<any>,
      required: true,}},setup(props) {
    const visible = ref(true);
    const close = () = > {
      visible.value = false;
      props.onClose({ text: "This is the child component's data." });
    };
    return () = > (
      <Modal
        title="Title"
        visible={visible.value}
        onCancel={close}
        onOk={()= >{message.success(" operation succeeded "); close(); }} > // Contents</Modal>); }});// Export wrapped components
export async function DynamicCellModal(props) {
  return DynamicModal(CellModal, props);
}

// Edit.tsx
// Use the component
import { DynamicCellModal } from "./CellModal";

DynamicCellModal({ props1: "".props2: {} }).then((data) = > {
  // data = {text: 'This is the child component's data'}
});
Copy the code

At this point someone noticed that when DynamicCellModal was called, props did not type check. Okay, arrangement

In this case, CellModal props contains three properties: onClose, props1, props2; Since onClose is dynamically forced by us, there is no need for the caller to pass it in. All props other than onClose need to be prompted

We need to get the props type of the component CellModal and then exclude onClose

  • First declare the type:
1️ ⃣// Get the props type of the component
type GetPropType<T extends new(... args:any) = >void> = {
  [P in keyof InstanceType<T>["$props"]]: InstanceType<T>["$props"][P];
};

2️ ⃣// Delete the onClose type
type DynamicPropType<T extends new(... args:any) = >void> = Omit<
  GetPropType<T>,
  "onClose"
>;
Copy the code
  • Use type (modify cellmodal.tsx file)
// CellModal.tsx

export async function DynamicCellModal(
  props: DynamicPropType<typeof CellModal>
) {
  return DynamicModal(CellModal, props);
}
Copy the code

At this point, the whole transformation is complete.

Add a scene
export const SuccessModal = defineComponent({
  setup() {
    2️ ⃣// This scenario needs to define two variables
    const success = ref({ msg: ' ' })
    const visible = ref(false)

    return () = > (
      <div>
        <Button
          onClick={()= >{1️ // Submit form, interface returns data, display modal display content submit(). Then ((res: {MSG: string }) => { visible.value = true success.value.msg = res.msg }) }} ><div class='text'>submit</div>
        </Button>
        <Modal show={visible.value}>
          <div>{success.value.msg}</div>
        </Modal>
      </div>)}})Copy the code

With our optimization, the code could look like this:

export const DynamicSuccessModal = defineComponent({
  setup() {
    return () = > (
      <Button
        onClick={()= > {
          submit().then((res: { msg: string }) => {
            DynamicModal({ msg: res.msg });
          });
        }}
      >
        <div class="text">submit</div>
      </Button>); }});Copy the code

We have any good way, welcome to discuss!