vue -- vue3利用createVNode函数,建立命令式调用组件

Published: · LastMod: April 07, 2024 · 755 words

vue3利用createVNode函数,建立命令式调用组件 🔗

常规我们使用vue组件都是引用式的,先使用import引用,再在components中注册 最后我们在template中使用

有时候就很难受,因为到处都在import

但是你看element-plusMessage组件可以直接用js命令式调用,那么我们组件可以做到吗

目标组件 🔗

比如我们有个目标组件,内部有个drawer,现在我希望在其他外部函数中使用时,直接使用open方法

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
<script lang="jsx">
import { defineComponent } from "vue";

export default defineComponent({
  setup(props) {
    return () => {
      return (
        <el-drawer title="Hello world">
          <h1>Dialog</h1>
        </el-drawer>
      );
    };
  },
});
</script>

createVNode 🔗

createVNode 的函数签名中, 第一个参数可以是动态组件,也可以是类组件,亦或是标签名字符串 第2个参数是props 第3个参数是children

最后返回值是VNode格式

1
2

declare function _createVNode(type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT, props?: (Data & VNodeProps) | null, children?: unknown, patchFlag?: number, dynamicProps?: string[] | null, isBlockNode?: boolean): VNode;

VNode 🔗

VNode中包含el属性,其实就是最后生成的dom节点

render函数 🔗

vue中暴露的render函数,可以动态去渲染VNode到具体节点上

1
export type RootRenderFunction<HostElement = RendererElement> = (vnode: VNode | null, container: HostElement, isSVG?: boolean) => void;

实现代码 🔗

目标组件

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
<script lang="jsx">
import { defineComponent } from "vue";

export default defineComponent({
  props: {
    visible: {
      type: Object,
    },
    close: Function,
    destroy: Function
  },
  setup(props) {
    return () => {
      return (
        <el-drawer title="Hello world" v-model={props.visible.value} onClosed={props.destroy}>
          <h1>Dialog</h1>
          <el-button onClick={props.close}>close</el-button>
        </el-drawer>
      );
    };
  },
});
</script>

这里createVNode传递的props需要传递一个响应式对象进去,如果直接传值,并不能响应

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
async function createDynamicComponent(instance) {
  const visible = ref(false);

  const open = () => {
    visible.value = true;
  };

  const close = () => {
    visible.value = false;
  };

  const Comp = await import("./components/dialog.vue");

  const container = document.createElement("div");
  container.id = "dialog-container";

  const vnode = createVNode(Comp.default, {
    name: "dy-dialog",
    visible,
    open,
    close,
    destroy: () => {
      container.remove();
    },
  }, null);

  vnode.appContext = instance.appContext;

  watch(visible, () => {
    console.log(`visible.value : ${visible.value}`);
  });

  render(vnode, container);
  instance.vnode.el.appendChild(container);

  return {
    instance: vnode,
    open,
    close,
  };
}

具体业务中使用

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
export default defineComponent({
  setup() {
    const instance = getCurrentInstance();
    const dyComp = ref(null);

    const gotoClick = async () => {
      dyComp.value = await createDynamicComponent(instance);
      dyComp.value.open();
    };
  }
})