web-dev-qa-db-ja.com

Webコンポーネント:子供と連携するには

現在、StencilJSを試し、いくつかのWebコンポーネントを作成しています。

これで<slot />と名前付きスロットなどがすべてあることがわかりました。 Reactから来たので、スロットはReactの子供に似ていると思います。 Reactでは、子供を使って多くのことができます。私がよくやったこと:

  1. 子供が提供されているかどうかを確認します
  2. 子を繰り返し処理して各子に何かをします(例:クラスでdivにラップするなど)

あなたはslot/web components/stencilJSを使ってそれをどのように行いますか?

私はステンシルで私の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>;
}

敬具

8
Schadenn

スロットを使用すると、レンダリング関数に条件を設定する必要がありません。子要素なし(例ではスパン)をスロット要素内に置くことができ、スロットに子が提供されていない場合は、スロット要素にフォールバックします。例えば:

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

あなたが書いたコメントに答える-あなたはそのようなことをすることができますが、いくつかのコーディングで、箱から出してはいけません。すべてのスロット要素にはassignedNodes関数があります。その知識とステンシルコンポーネントのライフサイクルの理解を使用して、次のようなことができます。

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文字列としてのみ使用されるため)。

11
Gil Fink

回答ギルありがとうございます。

以前に似たようなことを考えていました(状態の設定など-タイミングの問題が発生する可能性があるため)。ただし、コンポーネントがロードされた直後に別のロードをトリガーする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()の実行中、Hostの子要素はすでに削除されており、render()が返すスペースを確保するために、タイミングの問題が発生する可能性があると思いました。しかし、シャドウドムとライトドムがホストコンポーネント内にうまく共存しているので、問題はないはずです。

なぜあなたはinnerHTMLを使わなければならないのか本当にわかりません。 Reactから来る:私は慣れています:

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

そして、それは基本的なJSX構文であり、ステンシルも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>
        );
    }
}
_
1
Schadenn