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

Posted

技术标签:

【中文标题】Web 组件:如何与孩子一起工作?【英文标题】:Web components: How to work with children? 【发布时间】:2019-02-24 12:57:08 【问题描述】:

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

现在我知道有 <slot /> 和命名槽以及所有这些东西。来自 React,我猜 slot 类似于 React 中的 children。你可以在 React 中使用孩子做很多事情。我经常做的事情:

    检查是否提供儿童 迭代子项以对每个子项执行某些操作(例如,将其包装在带有类的 div 中等)

你会如何使用 slot/web components/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>;

亲切的问候

【问题讨论】:

【参考方案1】:

使用插槽,您无需在渲染函数中添加条件。您可以将 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>
        );
    

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

【讨论】:

好吧,这当然是真的。但这仍然不能解决将宿主组件的每个子组件包装到 - 例如 - - 元素中的问题。【参考方案2】:

感谢吉尔的回答。

我之前也在考虑类似的事情(设置状态等 - 因为可能会出现时间问题)。不过我不喜欢这个解决方案,因为您随后会在 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>
        );
    

【讨论】:

我的错。实际上,它应该是 componentWillLoad 而不是 componentDidLoad 只想导致一次重新渲染。关于您的解决方案,它没有使用 slot 元素,但它看起来仍然比我的肮脏解决方案更好。最后一件事,虽然 Stencil 和 React 使用 JSX,但它们呈现组件的方式存在差异。这就是为什么目前你不能像我们在 React 中那样只使用节点作为节点。

以上是关于Web 组件:如何与孩子一起工作?的主要内容,如果未能解决你的问题,请参考以下文章

Route 上的 404 页面无法按预期与孩子一起工作

如何使用reactjs解析从孩子到父母的数据?

悬停不与第 n 个孩子一起工作

如何让孩子爱上设计模式 —— 7.适配器模式(Adapter Pattern)

如何让孩子学会正确“玩”

打字稿样式组件错误:“类型'孩子:字符串;'与类型'IntrinsicAttributes'没有共同的属性。”