Веб-компоненты: как работать с детьми? - PullRequest
0 голосов
/ 20 сентября 2018

В настоящее время я экспериментирую с StencilJS для создания некоторых веб-компонентов.

Теперь я знаю, что есть <slot /> и именованные слоты и все такое.Исходя из React, я думаю, слот похож на детей в React.Вы можете делать много вещей, используя детей в React.Вещи, которые я часто делал:

  1. Проверьте, предоставлены ли какие-либо дети
  2. Итерируйте по детям, чтобы что-то сделать с каждым ребенком (например, оберните его в div с классом и т. Д.)

Как бы вы это сделали, используя слот / веб-компоненты / stencilJS?

Я могу получить элемент хоста моего веб-компонента в Stencil, используя

@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 голосов
/ 21 сентября 2018

Спасибо за ответ Gil.

Я думал о чем-то похожем раньше (установка состояния и т. Д. - из-за проблем с синхронизацией, которые могут возникнуть).Однако решение мне не понравилось, потому что тогда вы выполняете изменение состояния в 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() возвращает.Но так как shadow-dom и light-dom прекрасно сосуществуют в компоненте хоста, я думаю, что не должно быть никаких проблем.

Я действительно не знаю, почему вы должны использовать 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>
        );
    }
}
0 голосов
/ 21 сентября 2018

Используя слоты, вам не нужно помещать условие в вашу функцию рендеринга.Вы можете поместить элемент no children (в вашем примере span) внутрь элемента 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, а не как объекты).

...