如何正确解析Stencil.js子组件 正常渲染嵌套自定义组件
Stencil.js 标签页组件嵌套自定义元素渲染异常解决方案
问题根因
- 直接读取DOM元素的
innerHTML属性拿到的是静态HTML字符串,JSX语法下直接插值字符串时,不会对其中包含的自定义标签(比如示例中的custom-rating)做组件实例化和渲染,只会作为普通文本插入,自然无法正常展示自定义组件。 - 手动取
innerHTML拼接内容的思路本身不符合Stencil的组件渲染设计,额外还会带来XSS风险、丢失DOM事件绑定和组件状态的问题。 - 示例代码中两个
custom-tab都添加了active属性属于笔误,同一时间仅应给默认展示的标签添加该属性。
推荐实现方案(基于Stencil Slot机制)
Stencil原生提供的插槽(Slot)能力就是用来处理组件嵌套内容分发的,不需要手动解析DOM、拼接HTML,所有嵌套的原生元素、自定义组件都会被框架自动解析渲染,实现步骤如下:
custom-tab组件仅保留@Prop()装饰的业务属性(label、active等)即可,不需要编写render方法,它只作为内容承载的标记容器存在。- 在
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
相关产品推荐
相关产品推荐

