【问题标题】:How to access anchestor of nested web components?如何访问嵌套 Web 组件的祖先?
【发布时间】:2019-03-18 17:00:19
【问题描述】:

我正在为我的 Web 组件实现 Orchestrator 模式,如下所示:

<body>
  <my-controller>
    <div>
      <my-list>
        <span>
          <my-item></my-item>
        </span>
      </my-list>
    </div>
  </my-controller>
</body>

我创建的所有自定义元素都使用了 const root = super.attachShadow({mode: "open"}); root.appendChild(...); 的 Shadow DOM。

从我的内部 Web 组件中,我想在 connectedCallback() 中访问我的 my-controller 组件:

public connectedCallback(): void
    {
        if (this.isConnected)
        {
            for (let node = this.parentElement; node; node = node.parentElement)
                if (node instanceof ContainerBase)
                {
                    this._service = (<ContainerBase>node).GetService(this);
                    break;
                }

            if (this._service) this.Reset();
            else throw new ReferenceError(`${this.nodeName.toLowerCase()}: Couldn't find host element while connecting to document.`);
        }
    }


奇怪的是:我只能到达直接的父网页控件。


所以,如果在&lt;my-list&gt; 上调用connectedCallback(),我可以到达&lt;my-controller&gt;,但如果在&lt;my-item&gt; 上调用connectedCallback(),我只能到达&lt;span&gt;。当我使用&lt;my-item&gt; 开始搜索时,我什至无法到达&lt;my-list&gt;

即使我在调用 connectedCallback() 之后遍历 DOM 树,当我从 &lt;my-item&gt; 开始时,我也无法超越 &lt;span&gt;

这是故意的吗?

为什么可以从第一个嵌套组件访问外部 Web 组件,而我不能从第二个嵌套组件访问第一个嵌套 Web 组件?

如何从任何嵌套级别完全向上爬上 DOM 树?

【问题讨论】:

  • 可能是因为你使用了shadow dom?否则它会起作用。
  • AFAIK,Web 组件应始终使用 Shadow DOM。尴尬的是:node.shadowRootnull,而 node.parentElementnull 对于内部 Web 组件 - 并且对于 inner Web 组件。最外层的两个 Web 组件的行为符合预期。

标签: javascript google-chrome web-component shadow-dom custom-element


【解决方案1】:

当您使用 Shadow DOM 定义自定义元素内容时,您将创建一个独特的 DOM 树。 Shadow DOM 是一个没有根元素的 DocumentFragment。

因此,您无法简单地通过 parentElement 属性向上移动 DOM 来到达其(直观的)祖先。

要到达 Shadow DOM 的宿主元素,请改为将 use getRootNode()host 结合使用。

来自&lt;my-item&gt;connectedCallback()方法:

connectedCallback() {
   var parent = this.getRootNode().host
   console.log( parent.localNode ) // my-list
}

如果你想得到一个祖先,你可以试试这个recursive function

【讨论】:

【解决方案2】:

内部/子元素能够从外部/父元素访问数据通常被认为是不好的做法。

使用外部组件捕获的内部组件中的自定义事件更安全且耦合更少。

内部组件会派发一个事件,让外部元素知道它需要某些东西,然后外部组件可以调用一个函数或在内部组件上设置一个参数。

你可以这样做:

子元素

connectedCallback() {
  this.dispatch(new CustomEvent('request-service'));
}

set service(val) {
  this._service = val;
}

get service() {
  return this._service;
}

服务元素:

constructor() {
  super();
  this.addEventListener('request-service',
    evt => {
      evt.target.service = this.GetService(evt.target);
    }
  );
}

【讨论】:

  • 我不同意。事件驱动的方法是不利的:如果你在一个页面上有多个实例,它们都在监听同一个事件。您需要确保仅在冒泡阶段捕获这些事件。我认为体面的亲子关系没有缺点。这就是 DOM 树的意义所在。问题仍然存在:为什么嵌套在层次结构深处的 Web 控件的行为与外部的两个 Web 控件不同?
  • 不同意所有你想要的。但是,当您决定让父母和孩子彼此了解比他们更多的时候,您应该创建一个无法在其他任何地方使用的意大利面条式混乱和组件。您的代码可以轻松找出事件的来源。如果它不能,那么你应该重构它。一旦你允许孩子深入了解它的父级,你就允许将代码放置在错误的组件中,并且很难摆脱这种混乱。
  • 不,这取决于设计。例如,一个
  • 元素不能没有一个
      元素作为其父元素。有一个紧密的关系和这样的要求。在亲子关系中,如果没有父母提供服务,那么孩子就没有意义。另一方面,使用事件,你甚至不能保证有一个父级可以为它的子级提供服务。如果您想要严格的亲子关系要求,那么遵循这条路线并没有什么不好的做法。
  • 实际上&lt;li&gt; 元素可以存在于父元素之外。但是,如果我的代码有一个 DOM 依赖关系 只是为了将数据放入子元素中,我会重写它。使一个元素的脚本依赖于特定父元素的脚本的想法让我不寒而栗。我只是在提到一种具体且易于理解的做法。如果您想编写两个紧密耦合的元素,那是您的选择。但我认为这是个坏主意。
  • 只有一种“坏”实践,那就是没有实践。本质上意味着没有良好的实践。
  • 【解决方案3】:

    ShadowRoot 不是元素,ShadowRootparentNode 不是它的宿主元素。你需要照顾他们。

    function shadowIncludingParentElement(node) {
      if (node.parentElement)
        return node.parentElement;
      if (!node.parentNode)
        return null;
      if (node.parentNode.nodeType == Node.DOCUMENT_FRAGMENT_NODE)
        return node.parentNode.host;
      return null;
    }
    
    ...
    for (let node = this.parentElement; node;
        node = shadowIncludingParentElement(node)) {
      ...
    }
    

    【讨论】:

      猜你喜欢
      相关资源
      最近更新 更多
      热门标签