Веб-компоненты: как работать с детьми?
В настоящее время я экспериментирую с StencilJS для создания некоторых веб-компонентов.
Теперь я знаю, что есть <slot />
и названные слоты и все такое. Исходя из React, я думаю, слот похож на детей в React. Вы можете делать много вещей, используя детей в React. То, что я часто делал:
- Проверьте, есть ли дети
- Итерируйте детей, чтобы что-то сделать с каждым ребенком (например, оберните это в div с классом и т. Д.)
Как бы вы сделали это, используя слот / веб-компоненты /stencilJS?
Я могу получить Host Element моего веб-компонента в 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 ответа
Используя слоты, вам не нужно помещать условие в вашу функцию рендеринга. Вы можете поместить элемент no children (в вашем примере span) внутрь элемента slot, и если в слоте нет дочерних элементов, он отступит к нему. Например:
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, а не как объекты).
Спасибо за ответ, Гил.
Я думал о чем-то подобном раньше (установка состояния и т. Д. - из-за проблем с синхронизацией, которые могут возникнуть). Однако решение мне не понравилось, потому что тогда вы выполняете изменение состояния в 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>
);
}
}