You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确解析Stencil.js子组件 正常渲染嵌套自定义组件

Stencil.js 标签页组件嵌套自定义元素渲染异常解决方案

问题根因

  • 直接读取DOM元素的innerHTML属性拿到的是静态HTML字符串,JSX语法下直接插值字符串时,不会对其中包含的自定义标签(比如示例中的custom-rating)做组件实例化和渲染,只会作为普通文本插入,自然无法正常展示自定义组件。
  • 手动取innerHTML拼接内容的思路本身不符合Stencil的组件渲染设计,额外还会带来XSS风险、丢失DOM事件绑定和组件状态的问题。
  • 示例代码中两个custom-tab都添加了active属性属于笔误,同一时间仅应给默认展示的标签添加该属性。

推荐实现方案(基于Stencil Slot机制)

Stencil原生提供的插槽(Slot)能力就是用来处理组件嵌套内容分发的,不需要手动解析DOM、拼接HTML,所有嵌套的原生元素、自定义组件都会被框架自动解析渲染,实现步骤如下:

  1. custom-tab组件仅保留@Prop()装饰的业务属性(label、active等)即可,不需要编写render方法,它只作为内容承载的标记容器存在。
  2. 在custom-tabs父组件中拆分标签导航、内容面板两个渲染区域:
    • 标签导航:通过querySelectorAll拿到所有custom-tab实例,读取每个tab的属性生成可点击的导航项
    • 内容面板:直接使用默认插槽承载所有custom-tab节点,通过hidden属性控制不同tab的显隐,所有tab内部的嵌套组件会被Stencil自动初始化渲染

参考实现代码:

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

@Component({
  tag: 'custom-tabs',
  styleUrl: 'custom-tabs.css',
  shadow: true
})
export class CustomTabs {
  @Element() el: HTMLElement;
  @State() activeIndex: number = 0;
  private tabs: HTMLCustomTabElement[] = [];

  componentWillLoad() {
    this.tabs = Array.from(this.el.querySelectorAll('custom-tab'));
    // 读取初始激活的tab索引
    const defaultActive = this.tabs.findIndex(tab => tab.hasAttribute('active'));
    this.activeIndex = defaultActive >= 0 ? defaultActive : 0;
    // 初始化tab显隐状态
    this.tabs.forEach((tab, idx) => {
      idx === this.activeIndex ? tab.removeAttribute('hidden') : tab.setAttribute('hidden', '');
    })
  }

  private openTab(index: number) {
    this.activeIndex = index;
    this.tabs.forEach((tab, idx) => {
      idx === index ? tab.removeAttribute('hidden') : tab.setAttribute('hidden', '');
    })
  }

  render() {
    return (
      <div class="tabs-wrapper">
        {/* 标签导航栏 */}
        <div class="tab-nav" role="tablist">
          {this.tabs.map((tab, index) => (
            <button
              role="tab"
              class={{
                'tab-nav-item': true,
                'is-active': index === this.activeIndex
              }}
              onClick={() => this.openTab(index)}
            >
              {tab.getAttribute('label')}
            </button>
          ))}
        </div>
        {/* 内容区:通过slot自动分发所有custom-tab内容,嵌套组件自动渲染 */}
        <div class="tab-content">
          <slot />
        </div>
      </div>
    )
  }
}

配套添加少量CSS控制样式和显隐即可:

.tab-nav-item {
  padding: 8px 16px;
  border: none;
  background: transparent;
  cursor: pointer;
  border-bottom: 2px solid transparent;
}
.tab-nav-item.is-active {
  border-bottom-color: #165dff;
  color: #165dff;
}
custom-tab[hidden] {
  display: none !important;
}

该方案完全遵循Stencil的组件设计规范,所有放在custom-tab内的内容(原生HTML、自定义组件、第三方组件)都能正常渲染,不会出现纯文本输出的问题。

特殊场景备选方案(手动移动DOM节点)

如果因为业务限制不能使用Slot机制,禁止使用innerHTML拼接字符串,需要在componentDidLoad生命周期中直接操作原生DOM,把每个custom-tab下的真实childNodes移动到对应的内容面板容器中。这种方式会保留所有已初始化的组件实例、绑定的事件,不会破坏自定义组件的渲染逻辑,但可维护性远低于Slot方案,非必要不使用。

注意:任何场景下都不要把用户可控的内容通过innerHTML插入页面,避免触发XSS安全漏洞。

内容的提问来源于stack exchange,提问作者Ilya Shunko

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.30 11:36:21