当使用以下代码将 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)
}
我想知道为什么第一段代码不能正常工作?我应该如何纠正呢?谢谢!
经过几个小时的研究,我设法让它发挥作用。 这是您更新的代码:
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
});
},
})
受@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 事件分派。