将 Vue 3 组件包装到自定义元素中时无法发出事件

问题描述 投票:0回答:2

当使用以下代码将 Vue 3 组件包装到自定义元素中时,我注意到调用者未收到 Vue 事件。

import { createApp, defineCustomElement, getCurrentInstance, h } from "vue"

export const defineVueCustomElement = (component: any, { plugins = [] } = {}) =>
  defineCustomElement({
    styles: component.styles,
    props: component.props,
    emits: component.emits,
    setup(props, { emit }) {
      const app = createApp();
      plugins.forEach((plugin) => {
        app.use(plugin);
      });
      const inst = getCurrentInstance();
      Object.assign(inst.appContext, app._context);
      Object.assign(inst.provides, app._context.provides);
      return () =>
        h(component, {
          ...props,
        });
    },
  })

但是当我编写更简单的代码时,Vue事件可以被客户端正确接收。该代码的缺点是不支持Vue插件:

import { defineCustomElement } from "vue"

export const defineVueCustomElement = (component: any) => {
  defineCustomElement(component)
}

我想知道为什么第一段代码不能正常工作?我应该如何纠正呢?谢谢!

javascript vuejs3 web-component emit custom-events
2个回答
1
投票

经过几个小时的研究,我设法让它发挥作用。 这是您更新的代码:

export const defineVueCustomElement = (component: any, { plugins = [] } = {}) =>
  defineCustomElement({
    styles: component.styles,
    props: component.props,
    emits: component.emits,
    setup(props, { emit }) {
      const app = createApp();
      plugins.forEach((plugin) => {
        app.use(plugin);
      });
      const inst = getCurrentInstance();
      Object.assign(inst.appContext, app._context);
      Object.assign(inst.provides, app._context.provides);

      const events = Object.fromEntries(
        (component.emits || []).map((event: string) => {
          return [
            `on${ event[0].toUpperCase() }${ event.slice(1) }`,
            (payload: unknown) => emit(event, payload)
          ];
        })
      );

      return () =>
        h(component, {
          ...props,
          ...events
        });
    },
  })

0
投票

受@herobrine的启发,我设法让它与定义为异步的组件一起工作(因为我们不想一次“加载”所有Web组件,而是按需加载它们)。 所以给出

window.customElements.define(
        `org-custom-element`,
        defineCustomElement({
            // lazy loaded component (chunk)
            rootComponent: defineAsyncComponent(() => import('./MyComponent.ce.vue')),
            globalStyles: [globalStyles],
            plugins: {
                install(app: App) {
                    app.use(store);
                    app.use(i18n);
                },
            },
        })
    );

我们将

defineCustomElement
定义为

const extractEventNamesFromEmits = (
    emits
) => {
    if (Array.isArray(emits)) {
        return emits;
   }

    if (typeof emits === 'object') {
        return Object.keys(emits);
    }

    return [];
};

export const defineCustomElement = ({
  rootComponent,
  plugins,
  globalStyles = []
}) =>
  vueDefineCustomElement({
props: rootComponent.props,
styles: globalStyles,
setup(props, ctx) {
  const app = createApp()
  app.component("org-cpm-root", rootComponent)
  app.use(plugins)

  const inst = getCurrentInstance()

  Object.assign(inst.appContext, app._context)
  Object.assign(inst.provides, app._context.provides)

  rootComponent.setup?.(props, ctx)
  // important
  const isAsyncComponent = rootComponent.name === "AsyncComponentWrapper"
  const onVnodeMounted = isAsyncComponent
    ? vnode => {
        asyncVnode.value = vnode
      }
    : undefined

  const asyncVnode = ref(null)
  const events = computed(() => {
    const asyncComponentEmits =
      isAsyncComponent &&
      typeof asyncVnode.value?.type === "object" &&
      "emits" in asyncVnode.value.type
        ? asyncVnode.value.type.emits
        : null

    return Object.fromEntries(
      (
        extractEventNamesFromEmits(
          asyncComponentEmits ?? rootComponent.emits
        ) ?? []
      ).map(eventName => {
        return [
          `on${eventName[0].toUpperCase()}${eventName.slice(1)}`,
          payload => ctx.emit(eventName, payload)
        ]
      })
    )
  })

  return () =>
    h(rootComponent, {
      ...props,
      ...events.value,
      onVnodeMounted
    })
}
})

基本上,如果我们检测到组件名称是

AsyncComponentWrapper
,那么我们将监听正在安装的底层 VNode。安装后,我们将使用从此 VNode
emits
选项提取的事件重新渲染组件。它可能不适用于所有情况,但它适用于我们的情况,并允许正确“捕获”事件并将其作为自定义 DOM 事件分派。

© www.soinside.com 2019 - 2024. All rights reserved.