This is the first day of my participation in the August Text Challenge.More challenges in August

When using UI frameworks such as elementUI and antdUI, the Message global prompt method is used, while commonly used Vue components are written as tags.

<el-button type="primary">The main button</el-button>
<el-button type="success">Successful button</el-button>
Copy the code

However, Message and other components are used in a different way, which can be directly called by JS without introducing the Vue component of Message:

<template>
    <el-button type="text" @click="open">Click to open the Message Box</el-button>
</template>

<script>
    export default {
        methods: {
            open() {
                this.$confirm('This operation will permanently delete the file. Do you want to continue? '.'tip', {
                    confirmButtonText: 'sure'.cancelButtonText: 'cancel'.type: 'warning' 
                }).then(() = > {
                    this.$message({ type: 'success'.message: 'Deleted successfully! ' });
                }).catch(() = > {
                    this.$message({ type: 'info'.message: 'Cancelled delete'}); }); }}}</script>
Copy the code

How to use JS to call Vue components in Vue3?

Normal writing

Write a normal component first:

<template>
  <div class="test-comp">
    <div>title: {{title}}</div>
    <div>{{msg}}</div>
    <button @click="onClose">Shut down</button>
  </div>
</template>

<script lang="ts">
import { defineComponent } from 'vue'

export default defineComponent({
  props: {
    title: String.onClose: Function
  },
  setup() {
    const msg = 'This is a test component's own MSG'

    return {
      msg
    }
  },
})
</script>
Copy the code

Then call it within the parent component:

<template>
  <div>Js calls the Vue component</div>
  <button @click="isshow = true">Calling the test component</button>
  <TestComp
    v-if="isshow"
    title="This is the title that came in."
    :onClose="onClose"
  />
</template>

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

export default defineComponent({
  components: {
    TestComp
  },
  setup() {
    const isshow = ref(false)
    const onClose = () = > isshow.value = false

    return {
      isshow,
      onClose
    }
  },
})
</script>
Copy the code

Then look at the effect:

Overwriting JS calls

Next, rewrite the above component as a js call:

Before rewriting, there are a few more global apis you need to know about vue3:

  • DefineComponent: Returns a function or object converted to an object
  • H: Converts component objects into VNodes
  • Render: Mount vNode to node or remove node

Create another TS/js file and convert the TestComp component:

First implement a vNode mount node function and a node remove function:

import { defineComponent, h, render } from 'vue'

// Generate a unique key
const COMPONENT_CONTAINER_SYMBOL = Symbol('component_container')

/** * Create the component instance object * returns the same instance as getCurrentComponent() *@param {*} Component* /
function createComponent(Component: any, props: any, children: any) {
  / / create a vnode
  constvnode = h(Component, { ... props }, children)// Create a component container
  const container = document.createElement('div')
  // @ts-ignore Mounts the component container to vNode for subsequent removal
  vnode[COMPONENT_CONTAINER_SYMBOL] = container
  // Render vNode into a component container. In version VUe2, parent elements can be passed null, but vue3 does not
  render(vnode, container)
  // Returns the component instance
  return vnode.component
}

/** * Destroys the component instance object *@param {*} ComponnetInstance A component instance object obtained by createComponent */
export function unmountComponent(ComponnetInstance: any) {
  // Remove component node, render function first pass null, indicating remove action, will execute unmount method
  render(null, ComponnetInstance.vnode[COMPONENT_CONTAINER_SYMBOL])
}
Copy the code

Importing components to mount nodes

import TestComp from './testComp.vue'

/ /...
/ /...

// In the current scenario, you can omit this conversion step, but in terms of type, the value returned by defineComponent has a synthesized type constructor
const componentConstructor = defineComponent(TestComp)

// Create a variable to receive the created component instance
let instance: any;

// Create a node
const showTestComponent = (options: any) = > {
  // Create a component instance object
  instance = createComponent(componentConstructor, options, null)
  // Add to body
  document.body.appendChild(instance.vnode.el)
}

// options are props for the component
export const testComp = function (options: any) {
  const close = options.onClose
  // Rewrap close, add remove element operation
  options.onClose = () = > {
    close && close.call()
    unmountComponent(instance)
  }
  showTestComponent(options)
}
Copy the code

Finally called in the parent component

<template>
  <div>Js calls the Vue component</div>
  <button @click="show">Calling the test component</button>
</template>

<script lang="ts">
import { defineComponent } from 'vue'
import { testComp } from '.. /components/testComp'

export default defineComponent({
  setup() {
    const show = () = > {
      testComp({
        title: 'This is the title that came in.'.onClose() {
          console.log('close')}}}return {
      show
    }
  },
})
</script>
Copy the code

Ps: Effect picture:

At this point, the rewriting of the Vue component using JS calls is complete. This kind of component is very convenient in some scenarios. It can be wrapped according to the specific scenario and then called with one click.