引用Vue v3自定义元素组件中的主体元素

2022-08-15 00:00:00 vue.js web-component vuejs3
好的,我知道这个问题的一些变体已经被问到了,在Vue的不同版本和API中...但我一直没能弄明白,所以下面是我认为我的不同的原因:

我正在尝试构建以下组件:

  1. 内部足够复杂,使用Vue而不仅仅是native web components构建是有帮助的,但是...
  2. 将在页面上的Vue上下文之外运行(而不是在Vue应用程序中),因此打包为Web组件/自定义元素from Vue和...
  3. 实现将在<form>中使用的数据输入(同样,不是在Vue应用中)。

这方面的一个挑战是Vue Web组件使用影子DOM,而表单不会自动遍历影子根来查找输入:因此,让表单实际查看并提交组件的内部数据并不是自动的。

this helpful blog post中详细说明了一些希望:一个新的ElementInternals API和element-internals-polyfillNPM包,组件可以通过它指示数据到表单。实现与表单关联的自定义元素需要设置静态只读布尔属性(非常简单),但还需要链接类似以下内容:

// (`this` == the custom HTMLElement itself)
const _internals = this.attachInternals();

_internals.setFormValue(value);

问题是,我真的很难弄清楚我可以连接到哪里才能访问:

  • 挂载的DOM元素(影子根上的,即<my-custom-element>,不只是模板中的一些ref()),和
  • 要获取的组件的反应状态value
到目前为止,我主要使用的是Vue的composition and script setup APIs,这确实让人觉得它们使这一点变得更加困难:例如,onMounted根本没有定义this。但即使使用等价的选项APImounted: () => {},我也看到this.$el似乎是模板/影子根中的第一个元素,而不是拥有影子根的父自定义元素。

我还考虑了另一种方法--从创建的CustomElement类开始,尝试返回到有用的Vue数据和钩子……但在这里也找不到方法:

import { defineCustomElement } from "vue";
import MyCustomComponent from "./components/MyCustomComponent.ce.vue"
const MyCustomElement = defineCustomElement(MyCustomComponent);
class MyCustomElementFormAssoc extends MyCustomElement {
  static get formAssociated() {
    return true;
  }

  constructor(initialProps?: Record<string, any> | undefined) {
    super(initialProps);
    const _internals = this.attachInternals();

    // But here the component isn't even mounted yet - this._instance doesn't
    // exist and presumably reactive state doesn't either, so can't do:
    //   _internals.setFormValue(someValueState);
  }
}
customElements.define("my-custom-element", MyCustomElementFormAssoc);

因此,虽然一般来说,与其他Vue 3答案"there is no single root element and we should use refs instead"一致,但在我的例子中,我特别尝试访问定义组件的Custom元素-而不是模板内的元素。呈现的DOM如下所示:

    <my-custom-element class="this-one-is">
      #shadow-root (open)
      <div class="custom-element-template-can-have-multiple-roots"></div>
      <div class="but-these-are-not-the-elements-im-looking-for"></div>
    </my-custom-element>

有人知道怎么做吗?


解决方案

同意这是一种糟糕的代码气味,也是评估VUE是否真的适合一般用例的信号:使用并非完全原生但也不完全VUE的混合Web组件很可能成为维护负担,即使它现在可以工作。

但必须需要-我目前的解决方法是通过DOM从模板中的一些引用元素回溯(实际上并不重要),如下所示:

import { ref } from "vue";

const someTemplateRef = ref();

onMounted(() => {
  const hostNode = root.value.getRootNode()?.host;
});

相关文章