Web组件:如何与孩子一起工作?

时间:2018-09-20 09:00:22

标签: web-component stenciljs stencil-component

我目前正在尝试使用StencilJS创建一些Web组件。

现在我知道这里有<slot />个名为插槽的东西。来自React,我想插槽与React中的子代相似。您可以在React中使用子级来做很多事情。我经常做的事情:

  1. 检查是否提供了孩子
  2. 遍历孩子对每个孩子做一些事情(例如,将其包装在带有类的div中)

您将如何使用广告位/网络组件/ stencilJS呢?

我可以使用

在Stencil中获取Web组件的主机元素。
@Element() hostElement: HTMLElement;

我使用类似

的组件
<my-custom-component>
  <button>1</button>
  <button>2</button>
  <button>3</button>
</my-custom-component>

我想渲染类似的东西

render() {
  return slottedChildren ?
    <span>No Elements</span> :
    <ul class="my-custom-component">
      slottedChildren.map(child => <li class="my-custom-element>{child}</li>)
    </ul>;
}

亲切的问候

2 个答案:

答案 0 :(得分:7)

使用插槽,您无需在渲染函数中添加条件。您可以将no children元素(在您的示例中为span)放置在slot元素内,如果未向该slot提供任何子元素,它将退回到它的位置。 例如:

render() {
    return (
        <div>
            <slot><span>no elements</span></slot>
        </div>
    );
}

回答您所写的评论-您可以执行此操作,但需要一些编码,而不是立即可用。每个插槽元素都有一个assignedNodes函数。利用这些知识和对Stencil组件生命周期的理解,您可以执行以下操作:

import {Component, Element, State} from '@stencil/core';

@Component({
    tag: 'slotted-element',
    styleUrl: 'slotted-element.css',
    shadow: true
})
export class SlottedElement {
    @Element() host: HTMLDivElement;
    @State() children: Array<any> = [];

    componentWillLoad() {
        let slotted = this.host.shadowRoot.querySelector('slot') as HTMLSlotElement;
        this.children = slotted.assignedNodes().filter((node) => { return node.nodeName !== '#text'; });
    }

    render() {
        return (
            <div>
                <slot />
                <ul>
                    {this.children.map(child => { return <li innerHTML={child.outerHTML}></li>; })}
                </ul>
            </div>
        );
    }
}

这不是最佳解决方案,它将要求插槽的样式应将显示设置为none(因为您不想显示它)。 此外,它仅适用于仅需要呈现且不需要事件或其他任何东西的简单元素(因为它仅将它们用作html字符串而不用作对象)。

答案 1 :(得分:0)

谢谢你吉尔的答案。

我之前在想类似的事情(设置状态等-由于可能会出现计时问题)。不过,我不喜欢该解决方案,因为您要在componentDidLoad内进行状态更改,这会在组件加载后立即触发另一个加载。这似乎很脏而且不适合做。

innerHTML={child.outerHTML}的一点帮助对我很有帮助。

似乎您也可以轻松做到:

import {Component, Element, State} from '@stencil/core';

@Component({
    tag: 'slotted-element',
    styleUrl: 'slotted-element.css',
    shadow: true
})
export class SlottedElement {
    @Element() host: HTMLDivElement;

    render() {
        return (
            <div>
                <ul>
                    {Array.from(this.host.children)
                          .map(child => <li innerHTML={child.outerHTML} />)}
                </ul>
            </div>
        );
    }
}

我认为您可能会遇到计时问题,因为在render()期间,主机的子元素已被删除,以为render()返回的内容腾出空间。但是由于阴影域和光域在主机组件中可以很好地共存,所以我认为应该没有任何问题。

我真的不知道为什么您必须使用innerHTML。来自React,我很习惯:

{Array.from(this.host.children)
      .map(child => <li>{child}</li>)}

我认为这是基本的JSX语法,并且由于Stencil也使用JSX,所以我也可以这样做。虽然不起作用。 innerHTML帮了我大忙。再次感谢。

编辑:但是,如果您不使用shadow-dom,则会出现我提到的计时问题。一些奇怪的事情开始发生,最终您将有很多重复的孩子。 虽然可以做到(可能会有副作用):

import {Component, Element, State} from '@stencil/core';

@Component({
    tag: 'slotted-element',
    styleUrl: 'slotted-element.css',
    shadow: true
})
export class SlottedElement {
    children: Element[];

    @Element() host: HTMLDivElement;

    componentWillLoad() {
      this.children = Array.from(this.host.children);
      this.host.innerHTML = '';
    }

    render() {
        return (
            <div>
                <ul>
                    {this.children.map(child => <li innerHTML={child.outerHTML} />)}
                </ul>
            </div>
        );
    }
}